-
-
Notifications
You must be signed in to change notification settings - Fork 859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor DataTables factory #1488
Conversation
Here are my 2cts.
$someClass = new EloquentDataTable(); // could be anything, just an example
$engine = new CollectionDataTable($someClass); Imho above code should give an error like
That said, from a technical point of view I understand that the major difference between my PR and this PR is possibly the memory profile. Probably this PR uses a bit less memory, as using static factory methods on the DataTable engine class itself needs to load all DataTable engine classes into memory until it finds a matching one. |
@pimlie I think If the user pass unproper source to an engine, in my opinion, failure as error is just OK. I think you wont like the following code, right? $query = User::query();
if (QueryDataTable::canCreate($query)) {
$dataTable = QueryDataTable::create($query);
} else if (EloquentDataTable::canCreate($query)) {
$dataTable = EloquentDataTable::create($query);
} else {
abort(500);
}
return $dataTable->toJson(); If an engine needs exact type of data to create, type hinting in the constructor will be fine, like And for another example, let's see the built in public function __construct(Collection $collection)
{
$this->collection = $collection;
} Actually it can handle any type which could be converted to a
Now user can create a
I think PHP built-in class AwesomeDataTable implements DataTable
{
protected $source;
public function __construct($source)
{
$this->source = $source;
}
public function count()
{
if (is_array($source)) {
return count($source);
} else if (is_string($source)) {
return strlen($source);
} else if ($source instanceof Collection) {
return $source->count();
} else if ($source instanceof Builder) {
return $this->countForBuilder();
} else if ($source instanceof DbQuery) {
return $this->countForDbQuery();
} else if ...
}
} BTW, if we want to use For now, I can not think of any case that need variable parameters to create a DataTable engine. If there is, autoselect of For example: class CollectionDataTable extends \Yajra\DataTables\CollectionDataTable
{
}
class FooDataTable implements DataTable
{
public static function canCreate($data, $options)
{
return $data instanceof Collection && is_array($options);
}
}
class BarDataTable extends FooDataTable
{
} Since // Configuration 'engines' => [
'collection' => 'App\DataTables\CollectionDataTable',
'foo' => 'App\DataTables\FooDataTable',
'bar' => 'App\DataTables\BarDataTable',
],
'builders' => [
'Illuminate\Support\Collection' => 'collection'
], // DataTables::make $args = func_get_args();
foreach ($builders as $class => $engine) {
if ($source instanceof $class) {
return call_user_func_array([$engines[$engine], 'create'], $args);
}
}
foreach ($engines as $engine => $class) {
if (call_user_func_array([$engines[$engine], 'canCreate'], $args)) {
return call_user_func_array([$engines[$engine], 'create'], $args);
}
} For
Factory depended upon configuration is a common design. Think about Laravel Auth Guards, Cache, Database, Filesystems, Queue, etc. Users are free to config/override/remove any of built-in engines. So Everything is limited. I'd like to make it simple, clean, unify, easy to use, in a reasonable range. |
public function __construct($source)
{
if (! ($source instanceof Collection || is_array($source))) {
throw new InvalidArgumentException(...);
}
// ...
} |
I think most of our differences are just because we have a different preference on how to setup our code, I really dont think the constructor of a class should be the place that automagically converts sources. With regards to your comment about the This would imho be the preferred way of registering custom engines. Also read my comments in the readme about the order of adding the mongodb dataservice provider. This way a custom DataTable engine also has control over the order of datatable engines and can even insert itself between two existing datatables. I am sorry but I just really dont see the advantages of this PR except for the changes to the As I am sure that you and me will probably never get to agree about this, I guess its up to @yajra to decide what to do 😄 |
Yeah, different stands. |
@ElfSundae and @pimlie , thanks for the very informative exchange of comments. Maybe you could combine this together with #1462 I prefer using both of them. |
@ChaosPower I don't think combining is a good idea.
|
@ElfSundae I like both your PRs and can see combining them benefits more audience.
I like how the configs are now merged. /*
* DataTables accepted builder to engine mapping.
* This is where you can override which engine a builder should use.
*/
'builders' => [
'eloquent' => [
Illuminate\Database\Eloquent\Builder::class,
Illuminate\Database\Eloquent\Relations\Relation::class,
],
'query' => Illuminate\Database\Query\Builder::class,
'collection' => [
'array',
Illuminate\Support\Collection::class,
],
], |
@ChaosPower As mentioned before, please note that if a user at first decides to empty the collection builderrs config like this I agree with @ElfSundae that combining code (except for the changes to the singleton and macroable) is probably not really useful as we do mostly the same things just in a slightly different way. My PR is really DataTableEngine centric and this PR is more generally centered around DataTables config. Both have advantages and disadvantages and both have even more overlap with the other then we discussed until know. So eh, I guess its really up to @yajra to decide who is going to get the final 🏆 |
@pimlie Actually I don't understand clearly some of your thought such as "flexible", "full control using canCreate", and "make configuration easier", even I tried to accept for many times. @yajra merged your PR before, maybe he will be more able to agree with your point of view. So could you please submit a PR with patches such as "the changes to the singleton and macroable" ? Thanks. And I have several issues with your idea, these may be resolved to improve your PR:
|
Quite some remarks, see below
Hopefully to finally end this discussion, all the partially pseudo code below are (still) valid ways of getting a datatable: class eloquentUserModel extends Eloquent\Model {
use EloquentDataTableTrait;
}
$dataTables = new EloquentDataTable(eloquentUserModel::all());
$dataTables = eloquentUserModel::dataTables();
$dataTables = DataTables::eloquent(eloquentUserModel::all())
$dataTables = DataTables::make(eloquentUserModel::all())
$dataTables = datatables(eloquentUserModel::all())
In my opinion the following behaivour is also correct: $array = array_to_be_used_in_datatable();
// Throws error as array !== Collection
$dataTable = new CollectionDataTable($array);
// Throws error as array !== Collection or not? Up for discussion
$dataTable = DataTables::collection($array);
// Works as the create method converted the array to Collection. What a _convenience_! ;)
$dataTable = DataTables::make($array);
// Works, duh
$collection = new Collection($array);
$dataTable = new CollectionDataTable($collection);
$dataTable = DataTables::collection($collection); |
You are right, different personal preferences on coding. My preference is going to decouple the Please forgive my bad eyes, I can not see any helpful or strong reasons for adding To me, less is more. BTW, I was not nit-picking in my last point. Using the factory to create DataTable engines is the recommended way in the readme of this package and the Let's end this kind of discussion. Please submit your patched PR, let @yajra decide 👻 |
Thanks for your insights, this really is/was an interesting discussion! Maybe you look more to it from a developers point of view, someone who is actively working on custom engines? And my view is more from a users point of view, where the engines have already been created and its just about using them? I fully agree with you that using the service provider to register the engine is a bit more costly then including it in the datables config, but again convenience comes at a price. The reason I thought you were nitpicking was mostly because of what you said about the order of loading the service providers. Although I am happy to submit a PR, I will wait until @yajra speaks his mind about this 😄 One final note, could you maybe share your config that failed due to the missing |
Yeah, as I said, different stands. But our users are also developers. 😄 FYI: The order of package discovery may be from Sorry I was messed, I wanted to say |
Merged 8.0 branch. @yajra StyleCI analysis seems strange, see https://styleci.io/analyses/qv5b5V and https://styleci.io/analyses/zRb21j 'builders' => [
'eloquent' => [
Illuminate\Database\Eloquent\Builder::class,
Illuminate\Database\Eloquent\Relations\Relation::class,
],
'query' => Illuminate\Database\Query\Builder::class,
'collection' => [
'array',
Illuminate\Support\Collection::class,
],
], Is there something wrong with our StyleCI config? |
This will allow "dot" in an engine alias name.
Going to hold-off this atm since there are some breaking changes. Please target to master branch since this may need a major bump? Thanks! |
This will allow "dot" in an engine alias name.
…ables-fork into refactor-factory
Updated base branch to master. And I am thinking restore the |
Updated:
|
Updated PR description. |
It does not make sense to support number/bool/string for a DataTable engine, so I removed Now the |
Outdated and too many conflicts, may not be needed anymore on latest version. Thanks! |
In this PR, I would like to refactor a clean API for the
DataTables
factory.ref: #1462 (comment)
[BC] Exchange key and value for thebuilders
config.Builders should be mapped from engines to types because the engines are unique. This change will allow configuring different engines for the same type.Keys in thebuilders
config can be an engine alias which defined in theengines
config, or an engine class name.Engines in the
builders
config can be a class name now.Add support to specify any types in thebuilders
config.[BC] Change static methods
of()
andmake()
inDataTables
to instance methods. RemoveYajra\DataTables\DataTables::of
andYajra\DataTables\DataTables::make
usage.Since the DataTables has been registered as a singleton in the container, using the ugly (original DataTables) class directly is a bad design in this situation. Think about if the user or an extended package override
make()
method, the user must replace allDataTables::make
toCustomDataTables::make
.If someone prefers to access DataTables methods using static way, the facade is the best choice.
Add support to make DataTable instances using configured engines alias as
DataTables
methods.Remove unnecessary
canCreate
,create
interfaces for DataTable engines.To implement a new DataTable, just start with
__construct
.Remove unnecessary
Macroable
trait for theDataTables
.For now, the
DataTables
is just a factory to create DataTable instances, we do not want user to add macros to make it messy. If you want to register a custom engine, just add alias to theengines
configuration.Remove support of variable length parameters for
make()
.See [8.0] Fix datatables() helper #1487 (comment)
[TODO] Remove needless backward compatibility. @yajra
ExtendingNotesDataTables
factory depends upon the configuration ofengines
andbuilders
.engines
config just acts as class aliases, likeapp()->bind('eloquent', 'Yajra\DataTables\EloquentDataTable')
. Adding an engine alias is only needed if you want to use this alias as a method ofDataTables
to resolve the DataTable instances, likedatatables()->eloquent(...)
.make()
automatically decides an engine class by comparing the type of given data with types defined in thebuilders
config.There may be different engines for the same type, in this case we can not autoselect in anyway, so let the user (application) decide.make()
will use the first match.If you are developing a public package that contains some DataTable engines, it is not recommended to change/set/merge user'sdatatables.builders
configuration. Just document what you provide, and let user be free to use them.