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.
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.
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;
}
My Association model looks like this (irrelevant code redacted):
class Association extends Model
{
public function members() {
return $this->hasMany('App\Member');
}
}
My Member model looks like this:
class Member extends Model
{
public function scopeActive($query) {
return $query->where('membership_ended_at', Null);
}
public function scopeInactive($query) {
return $query->whereNotNull('membership_ended_at');
}
}
This is what I want to be able to do:
$association = Association::find(49);
$association->members->active()->count();
Now, I'm aware there's a difference between a Query and a Collection. But what I'm basically asking is if there's some kind of similar scope for collections. Of course, the optimal solution would be to not have to write TWO active methods, but use one for both purposes.
(question already answered in the comments, but might as well write a proper answer)
It is not possible to use a query scope in a Colletion, since query scope is a concept used in Eloquent to add constraints to a database query while Collections are just a collection of things (data, objects, etc).
In your case, what you need to do is to change this line:
$association->members->active()->count();
to:
$association->members()->active()->count();
This works because when we call members as a method, we are getting a QueryBuilder instance, and with that we can start chaining scopes to the query before calling the count method.
I have two classes that I use to access two different tables in my db. They both have a similar constructor that looks like that:
function __construct($db) {
$this->db = $db;
$userDAO = DAO_DBrecord::createUserDAO($this->db);
$this->userDAO = $userDAO;
}
The other class has the same constructor except that it uses createOtherTableDAO($this->db).
I am planning on having a couple other such classes, and it would be convenient if I could have them all inherit the same constructor, and pass createAppropriateTableDAO as an argument.
To clarify, in the first case above, createUserDAO($this->db) is a static function that calls a constructor in my DAO class. The function in the DAO looks as follows:
public static function createUserDAO($db) {
return new DAO_DBrecord($db, 'users');
}
I use this method to make sure the user model can only call a DAO on the users table.
I'm somewhat of a beginner, and I don't think I have ever seen anything like what I want.
Move the code to create the DAOs into a Factory and then inject the DAOs instead of hard coupling them into whatever these classes are supposed to represent. Or rather create the various Table Data Gateways ("classes that I use to access two different tables") as a whole in the Factory, e.g.
class TableDataGatewayFactory
…
public function create($gatewayName)
{
switch ($gatewayName) {
case 'user':
return new TableDataGateway(new UserDao($this->db)));
break;
default:
throw new Exception('No Gateway for $gatewayName');
}
}
}
As for $this->db, either pass that into the Factory via the ctor or move the creation into the Factory as well. It's somewhat doubled responsibility, but tolerable given that this Factory revolved around creating Database related collaborator graphs.
Apart from that: yes, call_user_func(array('ClassName', 'methodName')) would work. See the manual for
http://php.net/call_user_func and
http://php.net/manual/en/language.pseudo-types.php#language.types.callback
To answer your question first: No, you can't (without resorting to evilCode) pass a function name as a parameter.
But: What you want to archive is a poster-child-issue for an object oriented approach using inheritance.
You'd need a base-class:
class BaseClass
{
function __construct($db) {
$this->db = db;
}
}
and your implementations :
class MyClass extends BaseClass
{
function __construct($db) {
parent::__contruct($db);
$this->userDAO = DAO_DBrecord::createUserDAO($this->db);
}
}
Just for the record: the evilCode would have been
a) you could encapsulate your function in a create_function that can be used as an argument.
b) you could pass the function name as a string to your function and then pass it to eval in the receiving function.
But remember: When eval or create_function looks like the answer you're probably asking the wrong questions!
See: related question
There are several methods which you can use if you feel it necessary to pass the function name or indeed the function itself as a parameter of a function.
call_user_func($function,$args);
call_user_func is one of Php's native functions for invoking methods or functions which takes a function name and optional arguments parameter.
The functionality of call_user_func (when not pertaining to object methods) can be replicated without the using call_user_func using a variable with the string literal of the function name. For example:
function some_func()
{
echo "I'm a function!";
}
$function = "some_func";
$function(); /*Output: I'm a function!*/
And if you're feeling adventurous you can go a bit further and pass a closure / anonymous function as instead of the function name. For example:
$function = function()
{
echo "I'm another function!";
}
$function(); /*Output: I'm another function*/
You can achieve such behavior by using:
call_user_func
eval any literal
I need some advice on how I can proceed with this issue.
Using PHP
An example would be:
class BuilderClass {
function getClass($id, $some, $vars){
$dbResult = new db_Class::getDbRows($id, $some, $vars);
foreach(...)
// Build something from the database values
return self;
}
}
So what I want to do is to create a test case where I somehow mock the db results.
I have not found any great way to do this, please point me in the right direction or similar to get this working for me.
I could change something within the builder itself for example call a class that runs the function: FunctionRunner::runStaticFunction("db_Class", "getDbRows", $args, $something_else); But at the moment I don't know if that is possible neither. Any research articles that cover this or any sites that explain this. I'd appriciate anything at the moment.
Thanks
/Marcus
Split the operations of retrieving data from database, and building the data.
class BuilderClass {
function getClass($id, $some, $vars){
$dbResult = new db_Class::getDbRows($id, $some, $vars);
return doGetClass($dbResult);
}
function doGetClass($dbResult) {
foreach(...)
// Build something from the database values
return self;
}
}
That way, you can test doGetClass in isolation from calling the database .
As often the case, inability to easily write tests for your functions is caused by a flaw in your application design. In this case the db_Class is tightly coupled to your BuilderClass.
A proper solution would be to have a Database object in your BuilderClass using dependency injection, and mocking that injection to return a static result.
class BuilderClass
{
protected $oDatabase;
public function __construct(db_Class $oDatabase) {
$this->oDatabase = $oDataabse;
}
public function getClass($someVars) {
$this->oDatabase->getDbRows($someVars);
}
}
This way, the Database object is easily replaced with a stub.
There are many ways to do this, but since we are talking PHP, you could leverage the magic class loader function.
Simply put, if you want to mock the data access layer, you just create an object with the actual name of the data class, and the autoloader is never called.
Want to actually access the database? don't define the class and the autoloader will be called when something tries to access the database, which should then know what to do to load the class.
Mostly my autoloaders, when I use them, tend to look something like this;
function __autoload($className)
{
if(file_exists('../includes/'.$className.'.php'))
require_once('../includes/'.$className.'.php');
}