Skip to content

Instantly share code, notes, and snippets.

@JeffreyWay
Last active February 25, 2019 01:36
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JeffreyWay/5438984 to your computer and use it in GitHub Desktop.
Save JeffreyWay/5438984 to your computer and use it in GitHub Desktop.
Laravel challenge / poll for book.

Challenge: you need a flexible (swap in other repository implementations) way to display some kind of resource. Let's say, Posts. What does your folder structure look like?

Requirements:

  • Repository implements an interface
  • Must show at least one implementation of the interface (Eloquent version is fine)
  • The interface is injected into your PostsController.
  • Show where you register your IoC bindings
  • Show folder structure. Where are interfaces/repositories stored?
@fideloper
Copy link

Directory Structure:

# Inside of app/lib
Appname
    Storage
        Post
            PostInterface.php
            EloquentPost.php
    StorageServiceProvider.php

Interface:

    <?php namespace Appname\Storage\Post;

    interface PostInterface {

        public function getPosts($limit=20, $offset=0);

    }

Eloquent Implementation:

    <?php namespace Appname\Storage\Post;

    class EloquentPost implements PostInterface {

        protected $ormPost;

        public function __construct()
        {
            // Instance of Eloquent Post model
            $this->ormPost = new \Post;
        }

        public function getPosts($limit=20, $offset=0)
        {
            return $this->ormPost->where('status', 'published')
                            ->orderBy('created_at', 'desc')
                            ->take($limit)
                            ->skip($offset)
                            ->get();
        }

    }

IoC Binding:

<?php namespace Appname\Storage;

use Illuminate\Support\ServiceProvider;

class StorageServiceProvider extends ServiceProvider {

    /**
     * Register the binding
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('Appname\Storage\Post\PostInterface', 'Appname\Storage\Post\EloquentPost');
    }

}

Controller:

    # app/controllers/PostController.php
    <?php

    class PoastController {

        protected $post;

        public function __construct( Appname\Storage\Post\PostInterface $post )
        {
            $this->post = $post;
        }

        public function index()
        {
            $posts = $this->post->getPosts();

            return View::make('home', [ 'posts' => $posts ]);
        }

    }

@JeffreyWay
Copy link
Author

This is very similar to the way I do it! Just a couple differences, but mostly the same.

Anyone else approach it differently?

@ipalaus
Copy link

ipalaus commented Apr 22, 2013

/app/controllers/PostsController.php

<?php

class PostsController extends BaseController {

    protected $posts;

    public function __construct(Example\PostRepository $posts)
    {
        $this->posts = $posts;
    }

}

I will have an api package:

/src/Example/Api/Models/Post.php

<?php namespace Example\Api\Models;

class Post extends \Illuminate\Database\Eloquent\Model {}

/src/Example/Api/Repositories/PostInterface.php

<?php namespace Example\Api\Repositories;

class PostInterface {}

/src/Example/Api/Repositories/EloquentPostRepository.php (or namespaced Post/Eloquent.php)

<?php namespace Example\Api\Repositories;

class EloquentPostRepository implements PostInterface {}

/src/Example/Api/ApiServiceProvider.php

<?php namespace Example\Api;

use Illuminate\Support\ServiceProvider;

class ApiServiceProvider extends ServiceProvider {

    public function register()
    {
        $this->app->bind('Example\PostRepository', 'Example\Api\Repositories\EloquentPostRepository');
    }
}

I have to figure out how to develop this easily. Actually I'm doing it on a workbench but it's a nightmare when you switch to the composer.json package... I'm not sure how to handle this, thinking on best options too.

@ccovey
Copy link

ccovey commented Apr 22, 2013

To register the interface to the proper implementation I do just how Fideloper does it except I stick my ServiceProviders for my app in a ServiceProviders/ dir inside my app/AppName/ dir. My repo is a bit different but I don't think you care about that implementation so much.

@JeffreyWay
Copy link
Author

What about when dealing with basic CRUD methods that you'd like to make available across multiple repositories. For example EloquentPost and EloquentComment may both need a find() method. Do you duplicate that logic for each repository?

@JonoB
Copy link

JonoB commented Apr 22, 2013

Pretty much identical to @fideloper with a slightly different directory structure. I also don't instantiate a new model in the constructor of the EloquentPost

@JonoB
Copy link

JonoB commented Apr 22, 2013

@JeffreyWay Yes, I typically duplicate the find() method, since they will more often than not have somewhat different requirements (i.e. eager loading a join). I've played with having a common repo that gets extended, but found it not worth the effort....yet.

@JeffreyWay
Copy link
Author

@JonoB Yeah, I'm playing around with the same idea right now.

@ccovey
Copy link

ccovey commented Apr 22, 2013

Could you do an abstract repo and instead of using $ormPost in your constructor, do something like

class EloquentPostRepository extends RepositoryAbstract
{
    public function __construct(Models\Post $post)
    {
        $this->model = $post;
    }
}

abstract class RepositoryAbstract
{
    public function find($id)
    {
       return $this->model->find($id);
    }
}

@JeffreyWay
Copy link
Author

@ccovey Yep, that's exactly what I'm toying with. Each implementation would have its own Abstract class for common CRUD methods.

@fideloper
Copy link

@ccovey - Assuming you mean an abstract instead of an interface, I'd say:

Short answer: yes

Longer Answer: Interfaces are a better over-all use

The utility of Interfaces is the contract (the guarantee) that you'll have a method implemented. In your example, an Interface can guarantee that you'll have a find() method, and that method will take a parameter.

Extending classes can break that guarantee. Interfaces are therefore the better way to go.

It's a form of enforcement which leads to clearer code. However inheritance can mostly do that for you.

@JeffreyWay
Copy link
Author

Well, PostRepositoryInterface could still declare the contract.

<?php

interface PostRepositoryInterface {

    public function all();

    // ...

}

And then your implementation is free to extend any kind of abstract class without breaking that contract.

<?php namespace Repositories;


class EloquentPostRepository extends Eloquent implements PostRepositoryInterface {

   // all() is handled in the abstract class, Repositories\Eloquent
}

@ccovey
Copy link

ccovey commented Apr 22, 2013

@fideloper I said abstract :)

I was thinking more that you would have an interface with your contract and an abstract that would have common methods and implementations per repository. Not as a way to implement a contract just as a way to keep things DRY

@fideloper
Copy link

We're all in agreement then :D

@dannewns
Copy link

hey all just wondering if you might be able to help me out, i've just read through each example and its got me thinking that im now doing things wrong as i dont write my code in this way at all and that I should really start to, for the reasons mentioned above.

My main question is that within the post interface will I have to name all the methods that are default with Eloquent such as find or all() or even the type of whereField() methods? or can i just use them as you did with

$this->post::whereOrderNo($order_no)

sorry if I sound really stupid here, its just i've never really written code like the above and since starting using L4 its really made me think about how im doing things and how I can write better code which im very enjoyable!

thanks

Dan

@JonoB
Copy link

JonoB commented Apr 23, 2013

Dan, nothing wrong per se with the way that you are currently doing it. This is just better abstraction and allows for better testing. You are not looking to replicate eloquent - its just a way of moving the methods from your controller to the repo. Each method in the repo will contain an eloquent (or query builder) call - what's actually inside that call is totally up to you. I typically have something like the following for a resource type repo (removed some internal workings and been a bit more explicit than usual, but this will suffice):

class ContactRepository implements ContactRepositoryInterface
{
    public function all()
    {
        return Contact::with('type')->orderBy('name')->get();
    }

    public function find($id)
    {
        return Contact::findOrFail($id);
    }

    public function store($data = null)
    {
        $data = $data ?: Input::all();
        $contact = new Contact($data);

        return $contact->save();;
    }

    public function update($id, $data = null)
    {
        $data = $data ?: Input::all();
        $contact = $this->find($id);
        $contact->fill($data);

        return $contact->save();
    }
}

@dannewns
Copy link

@JonoB thanks for that it clarifies a lot of stuff for me. Id really like to start writing things that will be better for testing etc so seeing this kind of challenge put up by @JeffreyWay has really made me think about it all. Great work all and thanks again.

@dannewns
Copy link

Could this setup also be used for an email implementation for instance I know i can use Mail::send() whenever i want to send mail but when im testing using real data i would like any emails sent out to go to me as the developer and then when going live it to default to going to the correct user.

I could keep changing the to address each time and remember to set them back for go live but that seems like a bit of a hack, is there a way i could use an interface like above to create my own send method which then inside of that does the checking if im in dev mode and then uses a config to address if not it uses the to address passed to it.

Would the folder structure be along the lines of

# Inside of app/lib
Appname
    Email
       EmailInterface.php
       Email.php
    EmailServiceProvider.php

or does that look odd, or should i just be extending the Email class in the first place, sorry if im going off topic here.

@JonoB
Copy link

JonoB commented Apr 29, 2013

I have a question on this: how much do you abstract away from your controllers? For example, lets say that we are creating a user. Along with saving the user, we also want to automatically assign the user's group and fire off an email to the user. Do you:

  • Place all of the above in your UserRepository.store() method
  • Only save the user in Repository.store(). Return bool to controller - if true then save group (using UserGroup Repository) and then fire off the email in the controller
  • Something else?

@williamcolbert
Copy link

Love this challenge actually helping me with getting into abstraction.

But one question I have is , I see alot of people implementing for instance in this version posted, the Post class extending eloquent shouldn't this Post class be a entity that just defines what the Post is made up of and function to act on the entity and not extending Eloquent ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment