Coder Social home page Coder Social logo

ml-archive / magic-box Goto Github PK

View Code? Open in Web Editor NEW
46.0 31.0 3.0 2.22 MB

A magical implementation of Laravel's Eloquent models as injectable, masked resource repositories.

License: MIT License

PHP 100.00%
laravel-eloquent-models laravel-5-package api-framework

magic-box's Introduction

Laravel Magic Box

⛔️ DEPRECATED ⛔️

This project is no longer supported or maintained. If you need a modern version of Magic Box that is compatible with newer versions of Laravel please consider using the spiritual successor to this project — Koala Pouch.


Magic Box modularizes Fuzz's magical implementation of Laravel's Eloquent models as injectable, masked resource repositories.

Magic Box has two goals:
  1. To create a two-way interchange format, so that the JSON representations of models broadcast by APIs can be re-applied back to their originating models for updating existing resources and creating new resources.
  2. Provide an interface for API clients to request exactly the data they want in the way they want.

Installation/Setup

  1. composer require fuzz/magic-box

  2. Use or extend Fuzz\MagicBox\Middleware\RepositoryMiddleware into your project and register your class under the $routeMiddleware array in app/Http/Kernel.php. RepositoryMiddleware contains a variety of configuration options that can be overridden

  3. If you're using fuzz/api-server, you can use magical routing by updating app/Providers/RouteServiceProvider.php, RouteServiceProvider@map, to include:

    /**
     * Define the routes for the application.
     *
     * @param  \Illuminate\Routing\Router $router
     * @return void
     */
    public function map(Router $router)
    {
        // Register a handy macro for registering resource routes
        $router->macro('restful', function ($model_name, $resource_controller = 'ResourceController') use ($router) {
            $alias = Str::lower(Str::snake(Str::plural(class_basename($model_name)), '-'));
    
            $router->resource($alias, $resource_controller, [
                'only' => [
                    'index',
                    'store',
                    'show',
                    'update',
                    'destroy',
                ],
            ]);
        });
    
        $router->group(['namespace' => $this->namespace], function ($router) {
            require app_path('Http/routes.php');
        });
    }
  4. Set up your MagicBox resource routes under the middleware key you assign to your chosen RepositoryMiddleware class

  5. Set up a YourAppNamespace\Http\Controllers\ResourceController, here is what a ResourceController might look like .

  6. Set up models according to Model Setup section

Testing

Just run phpunit after you composer install.

Eloquent Repository

Fuzz\MagicBox\EloquentRepository implements a CRUD repository that cascades through relationships, whether or not related models have been created yet.

Consider a simple model where a User has many Posts. EloquentRepository's basic usage is as follows:

Create a User with the username Steve who has a single Post with the title Stuff.

$repository = (new EloquentRepository)
    ->setModelClass('User')
    ->setInput([
        'username' => 'steve',
        'nonsense' => 'tomfoolery',
        'posts'    => [
            'title' => 'Stuff',
        ],
    ]);

$user = $repository->save();

When $repository->save() is invoked, a User will be created with the username "Steve", and a Post will be created with the user_id belonging to that User. The nonsensical "nonsense" property is simply ignored, because it does not actually exist on the table storing Users.

By itself, EloquentRepository is a blunt weapon with no access controls that should be avoided in any public APIs. It will clobber every relationship it touches without prejudice. For example, the following is a BAD way to add a new Post for the user we just created.

$repository
    ->setInput([
        'id' => $user->id,
        'posts'    => [
            ['title' => 'More Stuff'],
        ],
    ])
    ->save();

This will delete poor Steve's first post—not the intended effect. The safe(r) way to append a Post would be either of the following:

$repository
    ->setInput([
        'id' => $user->id,
        'posts'    => [
            ['id' => $user->posts->first()->id],
            ['title' => 'More Stuff'],
        ],
    ])
    ->save();
$post = $repository
    ->setModelClass('Post')
    ->setInput([
        'title' => 'More Stuff',
        'user' => [
            'id' => $user->id,
        ],
    ])
    ->save();

Generally speaking, the latter is preferred and is less likely to explode in your face.

The public API methods that return models from a repository are:

  1. create
  2. read
  3. update
  4. delete
  5. save, which will either call create or update depending on the state of its input
  6. find, which will find a model by ID
  7. findOrFail, which will find a model by ID or throw \Illuminate\Database\Eloquent\ModelNotFoundException

The public API methods that return an \Illuminate\Database\Eloquent\Collection are:

  1. all

Filtering

Fuzz\MagicBox\Filter handles Eloquent Query Builder modifications based on filter values passed through the filters parameter.

Tokens and usage:

Token Description Example
^ Field starts with https://api.yourdomain.com/1.0/users?filters[name]=^John
$ Field ends with https://api.yourdomain.com/1.0/users?filters[name]=$Smith
~ Field contains https://api.yourdomain.com/1.0/users?filters[favorite_cheese]=~cheddar
< Field is less than https://api.yourdomain.com/1.0/users?filters[lifetime_value]=<50
> Field is greater than https://api.yourdomain.com/1.0/users?filters[lifetime_value]=>50
>= Field is greater than or equals https://api.yourdomain.com/1.0/users?filters[lifetime_value]=>=50
<= Field is less than or equals https://api.yourdomain.com/1.0/users?filters[lifetime_value]=<=50
= Field is equal to https://api.yourdomain.com/1.0/users?filters[username]==Specific%20Username
!= Field is not equal to https://api.yourdomain.com/1.0/users?filters[username]=!=common%20username
[...] Field is one or more of https://api.yourdomain.com/1.0/users?filters[id]=[1,5,10]
![...] Field is not one of https://api.yourdomain.com/1.0/users?filters[id]=![1,5,10]
NULL Field is null https://api.yourdomain.com/1.0/users?filters[address]=NULL
NOT_NULL Field is not null https://api.yourdomain.com/1.0/users?filters[email]=NOT_NULL

Filtering relations

Assuming we have users and their related tables resembling the following structure:

[
    'username'         => 'Bobby',
    'profile' => [
        'hobbies' => [
            ['name' => 'Hockey'],
            ['name' => 'Programming'],
            ['name' => 'Cooking']
        ]
    ]
]

We can filter by users' hobbies with users?filters[profile.hobbies.name]=^Cook. Relationships can be of arbitrary depth.

Filter conjuctions

We can use AND and OR statements to build filters such as users?filters[username]==Bobby&filters[or][username]==Johnny&filters[and][profile.favorite_cheese]==Gouda. The PHP array that's built from this filter is:

[
    'username' => '=Bobby',
    'or'       => [
          'username' => '=Johnny',
          'and'      => [
              'profile.favorite_cheese' => '=Gouda',
          ]	
    ]
]

and this filter can be read as select (users with username Bobby) OR (users with username Johnny who's profile.favorite_cheese attribute is Gouda).

Model Setup

Models need to implement Fuzz\MagicBox\Contracts\MagicBoxResource before MagicBox will allow them to be exposed as a MagicBox resource. This is done so exposure is an explicit process and no more is exposed than is needed.

Models also need to define their own $fillable array including attributes and relations that can be filled through this model. For example, if a User has many posts and has many comments but an API consumer should only be able to update comments through a user, the $fillable array would look like:

protected $fillable = ['username', 'password', 'name', 'comments'];

MagicBox will only modify attributes/relations that are explicitly defined.

Resolving models

Magic Box is great and all, but we don't want to resolve model classes ourselves before we can instantiate a repository...

If you've configured a RESTful URI structure with pluralized resources (i.e. https://api.mydowmain.com/1.0/users maps to the User model), you can use Fuzz\MagicBox\Utility\Modeler to resolve a model class name from a route name.

Testing

phpunit :)

TODO

  1. Route service provider should be pre-setup
  2. Support more relationships (esp. polymorphic relations) through cascading saves.
  3. Support paginating nested relations

magic-box's People

Contributors

fuzzjham avatar kfuchs avatar maidoesthings avatar simantovyousoufov avatar stristr avatar walterbm 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

Watchers

 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

magic-box's Issues

Not compatible with Laravel 5.4

Currently, this package is not compatible with Laravel 5.4.

$ composer require fuzz/magic-box

Using version ^1.1 for fuzz/magic-box
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Conclusion: don't install fuzz/magic-box 1.1.2
    - Conclusion: don't install fuzz/magic-box 1.1.1
    - Conclusion: remove illuminate/container v5.4.19
    - Installation request for fuzz/magic-box ^1.1 -> satisfiable by fuzz/magic-box[1.1, 1.1.1, 1.1.2].
    - Conclusion: don't install illuminate/container v5.4.19
    - fuzz/magic-box 1.1 requires illuminate/database 5.2.x -> satisfiable by illuminate/database[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database 5.2.x-dev requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.0 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.19 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.21 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.24 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.25 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.26 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.27 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.28 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.31 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.32 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.37 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.43 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.45 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.6 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - illuminate/database v5.2.7 requires illuminate/container 5.2.* -> satisfiable by illuminate/container[5.2.x-dev, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - Can only install one of: illuminate/container[5.2.x-dev, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.0, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.19, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.21, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.24, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.25, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.26, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.27, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.28, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.31, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.32, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.37, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.43, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.45, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.6, v5.4.19].
    - Can only install one of: illuminate/container[v5.2.7, v5.4.19].
    - Installation request for illuminate/container (locked at v5.4.19) -> satisfiable by illuminate/container[v5.4.19].


Installation failed, reverting ./composer.json to its original content.

Cant install with laravel 5.3

 Problem 1
    - Installation request for fuzz/magic-box ^1.1 -> satisfiable by fuzz/magic-box[1.1].
    - Conclusion: remove laravel/framework v5.3.28
    - Conclusion: don't install laravel/framework v5.3.28
    - fuzz/magic-box 1.1 requires illuminate/database 5.2.x -> satisfiable by illuminate/database[v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7].
    - don't install illuminate/database v5.2.0|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.19|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.21|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.24|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.25|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.26|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.27|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.28|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.31|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.32|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.37|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.43|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.45|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.6|don't install laravel/framework v5.3.28
    - don't install illuminate/database v5.2.7|don't install laravel/framework v5.3.28
    - Installation request for laravel/framework (locked at v5.3.28, required as 5.3.*) -> satisfiable by laravel/framework[v5.3.28].

image

Needs to be compatible with models that don't have auto-incrementing IDs.

Currently the way magic-box works, it does not allow for model creation with a unique ID. We need to check to make sure that when a model is being created with a unique ID, to allow the ID to be passed through. When the model is being updated, we need to remove the ID field so that it does not get overwritten.

Improve query modifiers API

Issue:
Adding modifiers to the repository current requires storing them in an intermediate array and then setting that array to overwrite all modifiers:

$modifiers = $repository->getModifiers();

// Add a modifier
$modifiers[] = function (Builder $query) { /* do some modifying */ };

// Set modifiers en masse
$repository->setModifiers($modifiers);

Proposal:
The most common use case would be served by an addModifier method on the repository which accepts a Callable containing the modifier.

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.