ServiceProvider design pattern - php

I'm working on a ServiceProvider build for the Silex framework and i have some questions about how i should organise my code (design pattern ect).
This is what i have so far:
/Package
/Package/Classes
/Package/Controller
/Package/Model
/Package/Provider
/Package/Package.php
Now come's the part where i question about my code:
In the Package.php i have a class thats creating the other needed classes:
It's some kind of package builder i think..
public function __construct()
{
$this->content = new ContentClass($this);
$this->language = new LanguageClass($this);
}
public function content()
{
return $this->content->getContent();
}
public function language()
{
return $this->language->getLanguage();
}
In my ServiceProvider i can now do things like this:
public function register(Application $app)
{
$app['package'] = $app->share(function($app)
{
return new Package();
});
$app['package.content'] = $app['package']->content();
$app['package.language'] = $app['package']->language();
}
I don't think this is the correct way to do this.
I'm searching for a design pattern or something like that to organize my code the correct way. I hope some one could help me out with this, thank you!

I am not sure what else the Package.php class does, but you also could let the Provider build all necessary parts.
The benefit here would be that you can pass a config array to the provider to build a specific configuration.
You can use one of the many configProvider like https://github.com/igorw/ConfigServiceProvider to manage configuration in f.e. yaml.
F.e.:
package:
content:
config1: foo
config2: bar
language: de
and use the provider as builder:
public function register(Application $app)
{
$app['package.content'] = $app->share(function($app)
{
return new ContentClass($app['package']['content']['config1'], $app['package']['content']['config2']);
});
$app['package.language'] = $app->share(function($app)
{
return new LanguageClass($app['package']['language']);
});
}

Related

Laravel singleton not working across controller/ViewComposer

In Laravel, I have a class that I would like to make available to the service controller, make some changes to in the controller action, and then render out with a ViewComposer.
I have done this several times before without issue, but for some reason this time my usual approach is not working - clearly I'm doing something different, and I'm beginning to suspect I've fundamentally misunderstood an aspect of what I am doing.
I have a ServiceProvider with this register() method:
public function register()
{
$this->app->singleton(HelperTest::class, function ($app) {
$pb = new HelperTest();
$pb->test = "jokes on you batman";
return $pb;
});
}
Then in my controller I'm doing the following:
private $helper;
public function __construct(HelperTest $pb)
{
$this->helper = $pb;
$this->helper->test = "hahah";
}
And then I have a viewcomposer doing the following:
private $helper;
public function __construct(HelperTest $pb)
{
$this->helper = $pb;
}
public function compose(View $view)
{
$view->with('output', $this->helper->test);
}
When I call {{ $output }} in the blade view, I expect to see hahah, but instead I get jokes on you batman.
My debugging has shown that all three of these methods are definitely being called. It looks to me like the ViewComposer is for some reason instantiating its own, fresh instance of the class. What am I doing wrong?
Thanks!
Execute php artisan optimize on your console, this will generate an optimized class loader for your application, then check if you can find your class HelperTest registered in services.php inside boostrap/cache. Until HelperTest is not registered there, Laravel IoC can't resolve your class.

Laravel lazy eager loading with repository

I started working with repositories in Laravel and came across a situation where I'm not sure if I'm handeling this the right way...
User repository:
interface UserRepositoryInterface
{
public function findByID(int $user_id);
public function load(User $user, array $relations);
}
class UserRepository implements UserRepositoryInterface
{
public function findByID(int $user_id)
{
return User::find($user_id);
}
public function load(User $user, array $relations)
{
$user->load($relations);
}
}
Client basecontroller:
protected $user_repository, $client;
class Controller extends BaseController
{
public function __construct(Request $request, UserRepositoryInterface $user_repository)
{
$this->user_repository = $user_repository;
$this->client = $this->user_repository->findByID($request->route('client_id'));
}
}
Some extension of the client basecontroller:
use App\Http\Controllers\...\Controller as ClientsController;
class SomeController extends ClientsController
{
public function index()
{
$this->user_repository->load($this->client, ['addresses', 'bank_accounts', 'etc']);
return $this->client;
}
}
While the index() functions does show the client with related models, it feels like my approach is wrong, but sinds I already have the client it feels more natural to load the missing related models then to do the call below (where I fetch the user again):
$this->client = $this->user_repository->findByIDWithRelations($user_id, ['...']);
Because the load() function in the repository doesn't return anything and I assign nothing in the index() function it feels bogus somehow... Can anyone confirm or deny this?
Update:
Take for example this piece of code below (does not work):
function addToArray($array, $value)
{
array_push($array, $value);
}
$array = ['a', 'b', 'c'];
addToArray($array, 'd');
foreach($array as $value)
{
echo $value;
}
The way I approached this in Laravel feels very similar, which makes me feel it's wrong :-).
While this topic may have different opinions, there's already some opinionated open-source packages that provides a simple but intuitive implementation of repositories for Laravel that allows you to use the following syntax for example:
$repository->with(['relationship']);
So that your repository is clean, all relations are lazy loaded unless you call the with method and pass an array to eager load some relations. The API of the following package is pretty simple & intuitive and I think it's going to help you in your approach.
Checkout: https://github.com/rinvex/repository/

Laravel Object Oriented

I am somewhat new to OOP, although I know about interfaces and abstract classes a bit. I have a lot of resource controllers that are somewhat similar in the bigger scheme of things, they all look like the example below, the only main difference is the index and what I pass to the index view.
What I simply need to know is, can I OO things up a bit with my resource controllers? For example, create one "main" resource controller in which I simply pass the correct instances using an interface for example? I tried playing around with this but I got an error that the interface wasn't instantiable, so I had to bind it. But that means I could only bind an interface to a specific controller.
Any advice, tips and pointers will help me out :)
class NotesController extends Controller
{
public function index()
{
$notes = Note::all();
return view('notes.index', compact('notes'));
}
public function create()
{
return view('notes.create');
}
public function show(Note $note)
{
return view('notes.show', compact('note'));
}
public function edit(Note $note)
{
return view('notes.edit', compact('note'));
}
public function store(Request $request, User $user)
{
$user->getNotes()->create($request->all());
flash()->success('The note has been stored in the database.', 'Note created.');
return Redirect::route('notes.index');
}
public function update(Note $note, Request $request)
{
$note->update($request->all());
flash()->success('The note has been successfully edited.', 'Note edited.');
return Redirect::route('notes.index');
}
public function delete($slug)
{
Note::where('slug', '=', $slug)->delete();
return Redirect::to('notes');
}
}
Note: Totally my opinion!
I would keep them how you have them. It makes them easier to read and understand later. Also will save you time when you need to update one to do something different from the rest. We tried this in a project I worked on and while granted it wasn't the best implementation, it is still a pain point to this day.
Up to you though. I'm sure people have done that in a way that they love and works great. Just hasn't been the case in my experience. I doubt anyone would look at your code though and criticize you for not doing it.
In Case you need to bind different Model instanses then you may use Contextual Binding, for example, put the following code in AppServiceProvider's register() method:
$this->app->when('App\Http\Controllers\MainController')
->needs('Illuminate\Database\Eloquent\Model')
->give(function () {
$path = $this->app->request->path();
$resource = trim($path, '/');
if($pos = strpos($path, '/')) {
$resource = substr($path, 0, $pos);
}
$modelName = studly_case(str_singular($resource));
return app('App\\'.$modelName); // return the appropriate model
});
In your controller, use a __construct method to inject the model like this:
// Put the following at top of the class: use Illuminate\Database\Eloquent\Model;
public function __construct(Model $model)
{
$this->model = $model;
}
Then you may use something like this:
public function index()
{
// Extract this code in a separate method
$array = explode('\\', get_class($this->model));
$view = strtolower(end($array));
// Load the result
$result = $this->model->all();
return view($view.'.index', compact('result'));
}
Hope you got the idea so implement the rest of the methods.

How to inject multiple classes that share the same interface in Laravel 4

Say I have an interface CrawlerInterface with implementation PageCrawler and FeedCrawler; if we happen to need both classes in a controller, how can that be achieved with constructor injection?
Previously we use a central ServiceProvider to register (i.e. App::bind) such classes, but in most cases we only have 1 implementation of an interface, so said problem hasn't occured to us yet.
PS: I also wonder if this problem suggests we should split the controller.
Updates:
Thanks for the comments and response, to explain, said interface has only one public method: crawl($uri), and both page/feed crawler implements it as given a resource identifier, return resource.
My follow up question:
Say we are in a calculator scenario where Addition, Subtraction and Multiplication share the same interface Operation, which has only 1 public method run, at some point we will still encounter this problem right? How do we handle situation like these in general with ServiceProvider?
If each crawler exists for a different reason, you can use arbitrary names for your instances, for example:
App::bind('crawler.allArticles', 'PageCrawler');
App::bind('crawler.latestArticles', 'FeedCrawler');
For the controller:
App::bind('CrawlerController', function($app) {
return new CrawlerController(
App::make('crawler.allArticles'),
App::make('crawler.latestArticles')
);
});
Your controller code would then use each crawler differently:
public function showLatestArticlesAction()
$latestArticles = $this->latestArticlesCrawler->crawl();
// ...
}
public function showAllArticlesAction()
$allArticles = $this->allArticlesCrawler->crawl();
// ...
}
If you just have a list of crawlers where each is used for the same thing, you probably want to do something like:
App::bind('crawlers', function($app) {
return [
App::make('PageCrawler'),
App::make('FeedCrawler'),
];
});
In your controller, you'll get a list of "crawlers" by configuring it like so:
App::bind('CrawlerController', function($app) {
return new CrawlerController(App::make('crawlers'));
});
Your controller code could be something like this:
public function showArticlesAction()
$allArticles = array();
foreach ($this->crawlers as $crawler) {
$allArticles = array_merge($allArticles, $this->crawler->crawl());
}
// ...
}
Ok lets assume you have a CrawlerController
class CrawlerController extends BaseController
{
protected $crawler1;
protected $crawler2;
public function __construct(CrawlerInterface $c1, CrawlerInterface $c2)
{
$this->crawler1 = $c1;
$this->crawler2 = $c2;
}
}
an interface
interface CrawlerInterface{}
and concrete implementations of that intefrace called PageCrawler and FeedCrawler
class PageCrawler implements CrawlerInterface{}
class FeedCrawler implements CrawlerInterface{}
You would inject the dependencies by writing a service locator like
App::bind('CrawlerController', function($app) {
$controller = new CrawlerController(
new PageCrawler,
new FeedCrawler
);
return $controller;
});
But as suggested by others you should rethink your logic, use it only if this kind
of architecture is unavoidable
I think that the interface won't help you in this case.
By doing:
App::bind('CrawlerInterface', '<implementation>');
You need to choose one:
App::bind('CrawlerInterface', 'PageCrawler');
or
App::bind('CrawlerInterface', 'FeedCrawler');
And then Laravel will inject it:
class CrawlerController {
public function __construct(CrawlerInterface $crawler)
{
}
}
To have both you have 2 options
-Have 2 different interfaces
-Inject the implementations directly:
class CrawlerController {
public function __construct(PageCrawler $pageCrawler, FeedCrawler $feedCrawler)
{
}
}
But I also think that, if you need something like this, you better rethink your logic.

Translate Silex routes

I'm trying to make urls translatable in my Silex app.
First, I tried overriding UrlGenerator and RedirectableUrlMatcher, but that didn't really work.
Then, I tried overriding:
$app['route_class'] = 'My\Translatable\Route';
with code like this:
class Route extends Silex\Route
{
public function setPattern($pattern)
{
return parent::setPattern(str_replace('admin', 'admin2', $pattern));
}
}
But I'm getting https://gist.github.com/6c60ef4b2d8d6584eaa7.
What is the right way to achieve this?
So the solution is to extend RedirectableUrlMatcher and overwrite match method instead of Route.
Matcher.php
class Matcher extends Silex\RedirectableUrlMatcher
{
public function match($pathInfo)
{
return parent::match(str_replace('/admin', '/', $pathInfo));
}
}
app.php
$app['url_matcher'] = $app->share(function () use ($app) {
return new Matcher($app['routes'], $app['request_context']);
});
Now when I'm accessing http://domain.com/admin silex returns content for http://domain.com/.
Hope this is what you need.

Categories