Goal: I want to "decorate" Laravel's Query Builder with additional functionality (without directly modifying it).
Example Problem: I'll try to keep this very brief. I have implemented a get method on my decorator:
public function get($columns = ['*'])
{
return $this->cache->get(implode('.', $columns), function () use ($columns) {
return $this->queryBuilder->get($columns);
});
}
I'm also delegating all calls to methods not implemented on the decorator to the Query Builder.
public function __call($method, $parameters)
{
return call_user_func_array([$this->queryBuilder, $method], $parameters);
}
Works fine when calling directly on the decorator, as you would expect. But almost everyone is used to chaining methods together when using the Query Builder.
$queryBuilder = (new CachingDecorator( new QueryBuilder , $app['cache.store'] ));
// get all users
$queryBuilder->from('users')->get();
// get one user
$queryBuilder->from('users')->first(); // <-- delegates to get() internally
Problem: The results from the call directly above are not being cached. Obviously because the from method returns an instance of the Laravel Query Builder and not my decorator.
Question: Is there some helpful pattern that will help solve this? Or is this a limitation of the decorator pattern?
My first thought was to try to bind $this to another object, like you can in Javascript. I don't think PHP allows this.
The best solution I can come up with involves a class to map the query builder object to its decorator(s), and/or some sort of base decorator that re-implements almost every method in the query builder object (not a fan of this one as it totally throws the DRY principle out the window).
Additional Notes: I know I could side-step the issue by just not chaining method calls together. No brainer right? Except it is not reasonable to ask every developer on the team to avoid chaining their calls together. I would much rather solve this problem than side-step it.
You should return your decorator from the __call method:
public function __call($method, $parameters)
{
$result = call_user_func_array([$this->queryBuilder, $method], $parameters);
return $result === $this->queryBuilder ? $this : $result;
}
If you're using PHP 5.6+, you can use the spread operator to clean this up a bit:
public function __call($method, $parameters)
{
$result = $this->queryBuilder->$method(...$parameters);
return $result === $this->queryBuilder ? $this : $result;
}
Related
I'd like to instantiate multiple (i.e. of the same type) different (i.e. differing combination of attributes) complex (i.e. many optional parameters) objects in my code.
I think that using a Builder as described by Joshua Bloch here and talked about over here is the best way to go.
A very basic implementation would look like this:
class ObjectBuilder implements ObjectBuilderInterface
{
public function setAttributeA( $value ) : self
{
$this->attributeA = $value;
return $this;
}
public function setAttributeB( $value ) : self
{
$this->attributeB = $value;
return $this;
}
.
.
.
public function build() : Object
{
return new Object ($this->attributeA, $this->attributeB,...);
}
}
BUT as far as I understand the inner workings of a builder, by using the setters on the builder object the builder itself has a state and couldn't be reused for another object without unsetting all attributes first.
A use case for example in a controller would look something like this:
public function FooController (ObjectBuilderInterface $builder, ...)
{
$builder
->setAttributeA('A')
->setAttributeB('B');
$instance1 = $builder->build();
$builder
->setAttributeA('C')
->setAttributeB('D');
$instance2 = $builder->build();
}
One could make an unset call at the end of build() but that feels somewhat wrong to me and in my above mentioned sources aswell as everywhere I looked for a real life implementation of such a builder (I live in Symfony-land and had a look into FormBuilder for example) was anything like an unset() used.
How is a Builder used to instantiate multiple different instances?
I don't think there's a "right" answer here, but you have a few options.
Create a new builder for each object you intend to build e.g. from a factory.
Add the unset method you mention on the builder.
Reset the internal state of the builder each time you build.
Add a fluent startOver or newInstance member to your builder implementation, giving a clear way to indicate "this is a fresh start" at the call-site.
I would like to extend Eloquent Builder to have a support for search function. Inside my service provider I've added the following:
Builder::macro('search', function (string $searchBy) {
...
}
which works. I can now call a search method on my model.
The issue I'm having is that the logic within it is getting a rather complex and I wouldn't like it to be a single large script, however splitting to functions doesn't work since scope when inside a macro callback is actually from Eloquent Builder.
So this doesn't work:
public function foo()
{
dd('bar');
}
public function boot()
{
Builder::macro('search', function (string $searchBy) {
$this->bla();
...
}
}
Is there a way to use functions without going over the hassle of extending complete Eloquent Builder?
I have ended up creating a class which would contain the complete logic I need. I see this as a fairly good OOP practice.
Builder::macro('search', function (array $input) {
$jsonQuery = new JsonQuery($this, $input);
$jsonQuery->search();
return $this;
});
For anyone interested in details, you can check out my JSON search package.
I'm rather new to the PHP Mockery framework, and attempting to test multiple functions on a mocked service request function. The different functions send the same variable names with different values. I'd like to test to make sure each case is hitting the mocked class. Can I receive multiple arguments with my mocked function and return each of them to the tester function?
The function to test:
public function doSomething() {
$result = $this->serviceRequest('stuff', '/url', []);
return $result;
}
The pseudocode for testing function:
public function testDoSomething() {
$service_mock = $this->mockService();
$this->assertEquals($service_mock->doSomething(), $returnedArgs);
}
I'd ideally like to take those three arguments being sent to the mock, return them with the mock's andReturn() or something similar to be asserted in the original test method (to verify they are being sent to the mock). Is there a good way to do this or a common practice I'm missing?
Right now, my mock is looking similar to this pseudocode (and those 3 arguments are being validated in closure $args):
$client->shouldReceive('serviceRequest')->withArgs($args)->once()->andReturnUsing($args)->getMock();
Thanks!
You can use a callback to do this:
The below example is adapted form the Mockery docs, although it uses it as an example for reference passing, it can be used for any custom expectations.
$m->shouldReceive('insert')->with(
\Mockery::on(function(&$data) use($logger) {
if (!is_array($data)) return false;
$data['_id'] = 123;
$logger->logParameters('insert', $data);
return true;
}),
\Mockery::any()
);
http://docs.mockery.io/en/latest/reference/pass_by_reference_behaviours.htm
I'm newbye of OOP and Laravel , but i have noticed that the operator with in laravel is used for a lot of things: for example i have found this part of code in the documentation:
return Redirect::to('register')->withErrors($validator);
withErrors? I know that with is used to pass multiple element to a view: is there more that i need to know about this operator?
Another Question: the validate class in php have this type of operator:
$validator->required('You must supply an email address.')->email('You must supply a valid email address')->validate('email', 'Email');
why i can use multiple "->" for a instance of the class validator? I know that in this way:
$object = new object();
$object->something(); //metod or attribute
but i know that it's impossible use the operator "-" for indicate multiple method/attribute for a class(even if it is a library).
thank you for all and i'm sorry for,maybe,the stupid question! Thank you!
Well Redirect::to() returns an instance of Illuminate\Http\RedirectResponse. This class has a method withErrors which is called in the example.
However this with* method is a bit different than the one to pass data to a view. withErrors flashes the data to the session so it is available for the next request (after the redirect happens)
Regarding your second question, this: $validator->required()->email() is called method chaining. It is used a lot (not only in Laravel) to achieve a nice and brief syntax.
What happens is that the methods return $this so you can call the next method on the same object right away.
Let's look at another example:
$result = User::where('active', true)->where('name', 'like', 'a%')->get();
If we now take a look at the where() method in Illuminate\Database\Eloquent\Builder you can see that after the where logic happens it returns $this so we can continue calling methods.
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// code omitted for brevity
return $this;
}
Many Laravel classes implement this so-called Fluent Interface that allows method chaining for nearly every function call. Only methods that have an obvious return value can't be chained. Like retrieving a result with get(). Of course it has to return the result and it can't return $this as well.
You seem to understand the purpose of with() just fine.
As for your second question, you can "chain" object methods if the methods each return the object like this:
<?php
class MyClass {
public function myMethod()
{
// do stuff
return $this;
}
In PHP laravel, we have codes like
$user = User::find(1);
var_dump($user->name);
I am not concerning how to use the find method, I am concerning why laravel use a static method? Shouldn't the use of static method make the method hard to test?
Will it be better if they designed using singleton?
e.g.
$user = User::getInstance()->find(1);
var_dump($user->name);
In fact, your example is very similar to what Laravel does behind the scene. When you do User::find(), you are actually asking for a new instance, either an instance of Collection or a QueryBuilder.
Illuminate\Database\Eloquent\Model (reference):
public static function find($id, $columns = array('*'))
{
if (is_array($id) && empty($id)) return new Collection;
$instance = new static;
return $instance->newQuery()->find($id, $columns);
}
As a side note, you'll also see another way of using static methods in Laravel e.g. Input::get(). These are called Facades.
Facades provide a "static" interface to classes that are available in the application's IoC container ... Laravel "facades" serve as "static proxies" to underlying classes in the IoC container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.
When a user references any static method on the ... facade, Laravel resolves the cache binding from the IoC container and runs the requested method (in this case, get) against that object.
You can read more about Larave's Facades at: http://laravel.com/docs/facades
Unnawut has a very good answer, however I felt it necessary to add in further explanation.
In your example
$user = User::find(1);
var_dump($user->name);
Laravel isn't using a static method, you are. Another way to do this which you are probably looking for is to use dependency injection, which Laravel makes very easy because it can be done automatically. So in whatever class you are using your User model in, you should be setting up something like this in the constructor...
public function __construct(User $user)
{
$this->user = $user;
}
And then you can modify your code to not use the static bindings.
$user = $this->user->find(1);
var_dump($user->name);
This would restrict the system from only having one User. Whilst the find method may be static, the User class will have other methods and properties that aren't, a likely example is in your example: $user->name
A method that does not rely upon any instance variables, I.e variables who's value is specific to the particular object instance, but instead provides generic functionality that applies to all instances, can, and probably should, be static. This is why the $this operator is illegal within static methods, as it can make no reference to a particular object instance.
#unnawut reference link to the source code of Model.php is no longer using static find function as the class has been refactored. The function that get called eventually is in Builder.php (source code),
The logic still same
- magic function `__callStatic` of Model class get called
- a new instance of Builder is created and return
- call the find method of the Builder instance
Model.php
public function newEloquentBuilder($query)
{
return new Builder($query);
}
Builder.php
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
return $this->whereKey($id)->first($columns);
}