Skip to content

Commit f9c0323

Browse files
committed
adding ColumnUtillities with test
1 parent 6735b92 commit f9c0323

File tree

3 files changed

+300
-3
lines changed

3 files changed

+300
-3
lines changed

src/Utilities/ColumnUtilities.php

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<?php
2+
3+
namespace Rappasoft\LaravelLivewireTables\Utilities;
4+
5+
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Query\Builder as Builder;
8+
use Illuminate\Support\Str;
9+
10+
class ColumnUtilities
11+
{
12+
/**
13+
* Grab the relation part of a column
14+
*
15+
* @param $column
16+
* @return bool
17+
*/
18+
public static function hasRelation($column)
19+
{
20+
return Str::contains($column, '.');
21+
}
22+
23+
/**
24+
* Grab the relation part of a column
25+
*
26+
* @param $column
27+
* @return string
28+
*/
29+
public static function parseRelation($column)
30+
{
31+
return Str::beforeLast($column, '.');
32+
}
33+
34+
/**
35+
* Grab the field part of a column
36+
*
37+
* @param $column
38+
* @return string
39+
*/
40+
public static function parseField($column)
41+
{
42+
return Str::afterLast($column, '.');
43+
}
44+
45+
/**
46+
* Is the column selected?
47+
*
48+
* @param $column
49+
* @param $searchColumns
50+
* @return bool
51+
*/
52+
public static function hasMatch($column, $searchColumns)
53+
{
54+
return array_search($column, $searchColumns ?? []) !== false;
55+
}
56+
57+
/**
58+
* Is the column selected by a wildcard match?
59+
*
60+
* @param $column
61+
* @param $searchColumns
62+
* @return bool
63+
*/
64+
public static function hasWildcardMatch($column, $searchColumns)
65+
{
66+
return count(array_filter($searchColumns ?? [], function ($searchColumn) use ($column) {
67+
68+
// match wildcards such as * or table.*
69+
$hasWildcard = Str::endsWith($searchColumn, '*');
70+
71+
// if no wildcard, skip
72+
if (! $hasWildcard) {
73+
return false;
74+
}
75+
76+
if (! self::hasRelation($column)) {
77+
return true;
78+
} else {
79+
$selectColumnPrefix = self::parseRelation($searchColumn);
80+
$columnPrefix = self::parseRelation($column);
81+
82+
return $selectColumnPrefix === $columnPrefix;
83+
}
84+
85+
})) > 0;
86+
}
87+
88+
/**
89+
* @param EloquentBuilder|Builder $queryBuilder
90+
* @return null
91+
*/
92+
public static function columnsFromBuilder($queryBuilder = null)
93+
{
94+
if ($queryBuilder instanceof EloquentBuilder) {
95+
return $queryBuilder->getQuery()->columns;
96+
} elseif ($queryBuilder instanceof Builder) {
97+
return $queryBuilder->columns;
98+
} else {
99+
return null;
100+
}
101+
}
102+
103+
/**
104+
* Try to map a given column to an already selected column
105+
*
106+
* @param $column
107+
* @param $queryBuilder
108+
* @return string
109+
*/
110+
public static function mapToSelected($column, $queryBuilder)
111+
{
112+
// grab select
113+
$select = self::columnsFromBuilder($queryBuilder);
114+
115+
// can't match anything if no select
116+
if (is_null($select)) {
117+
return null;
118+
}
119+
120+
// search builder select for a match
121+
$hasMatch = self::hasMatch($column, $select);
122+
123+
// example 2 - match
124+
// column: service_statuses.name
125+
// select: service_statuses.name
126+
// maps to: service_statuses.name
127+
128+
// if we found a match, lets use that instead of searching relations
129+
if ($hasMatch) {
130+
return $column;
131+
}
132+
133+
// search builder select for a wildcard match
134+
$hasWildcardMatch = self::hasWildcardMatch($column, $select);
135+
136+
// example 3 - wildcard match
137+
// column: service_statuses.name
138+
// select: service_statuses.*
139+
// maps to: service_statuses.name
140+
141+
// if we found a wildcard match, lets use that instead of matching relations
142+
if ($hasWildcardMatch) {
143+
return $column;
144+
}
145+
146+
// split the relation and field
147+
$hasRelation = self::hasRelation($column);
148+
$relationName = self::parseRelation($column);
149+
$fieldName = self::parseField($column);
150+
151+
// we know there is a relation and we know it doesn't match any of the
152+
// select columns. Let's try to grab the table name for the relation
153+
// and see if that matches something in the select
154+
//
155+
// example 4 - relation to already selected table
156+
// column: serviceStatus.name
157+
// select: service_statuses.name
158+
// maps to: service_statuses.name
159+
160+
// if we didn't previously match the column and there isn't a relation
161+
if (! $hasRelation) {
162+
163+
// there's nothing else to do
164+
return null;
165+
166+
// this is easiest when using the eloquent query builder
167+
} elseif ($queryBuilder instanceof EloquentBuilder) {
168+
169+
$relation = $queryBuilder->getRelation($relationName);
170+
$possibleTable = $relation->getModel()->getTable();
171+
172+
} elseif ($queryBuilder instanceof Builder) {
173+
174+
// @todo: possible ways to do this?
175+
$possibleTable = null;
176+
177+
} else {
178+
179+
// we would have already returned before this is possible
180+
$possibleTable = null;
181+
182+
}
183+
184+
// if we found a possible table
185+
if (! is_null($possibleTable)) {
186+
187+
// build possible selected column
188+
$possibleSelectColumn = $possibleTable . '.' . $fieldName;
189+
190+
$possibleMatch = self::hasMatch($possibleSelectColumn, $select);
191+
192+
// if we found a possible match for a relation to an already selected
193+
// column, let's use that
194+
if ($possibleMatch) {
195+
return $possibleSelectColumn;
196+
}
197+
198+
$possibleWildcardMatch = self::hasWildcardMatch($possibleSelectColumn, $select);
199+
200+
// ditto with a possible wildcard match
201+
if ($possibleWildcardMatch) {
202+
return $possibleSelectColumn;
203+
}
204+
}
205+
206+
// we couldn't match to a selected column
207+
return null;
208+
}
209+
}

tests/ColumnUtilitiesTest.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
namespace Rappasoft\LaravelLivewireTables\Tests;
4+
5+
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
6+
use Illuminate\Support\Collection;
7+
use Rappasoft\LaravelLivewireTables\DataTableComponent;
8+
use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\PetsTable;
9+
use Rappasoft\LaravelLivewireTables\Tests\Models\Pet;
10+
use Rappasoft\LaravelLivewireTables\Utilities\ColumnUtilities;
11+
12+
class ColumnUtilitiesTest extends TestCase
13+
{
14+
protected $query;
15+
16+
public function setUp(): void
17+
{
18+
parent::setUp();
19+
}
20+
21+
/** @test */
22+
public function test_parse_relation(): void
23+
{
24+
$this->assertFalse(ColumnUtilities::hasRelation('id'));
25+
$this->assertTrue(ColumnUtilities::hasRelation('pets.id'));
26+
$this->assertEquals('pets', ColumnUtilities::parseRelation('pets.id'));
27+
$this->assertEquals('pets.breeds', ColumnUtilities::parseRelation('pets.breeds.id'));
28+
}
29+
30+
/** @test */
31+
public function test_parse_field(): void
32+
{
33+
$this->assertEquals('id', ColumnUtilities::parseField('pets.id'));
34+
}
35+
36+
/** @test */
37+
public function test_has_match(): void
38+
{
39+
$this->assertTrue(ColumnUtilities::hasMatch('id', ['id', 'name']));
40+
$this->assertTrue(ColumnUtilities::hasMatch('name', ['id', 'name']));
41+
}
42+
43+
/** @test */
44+
public function test_has_wildcard_match(): void
45+
{
46+
$this->assertTrue(ColumnUtilities::hasWildcardMatch('id', ['*']));
47+
$this->assertTrue(ColumnUtilities::hasWildcardMatch('pets.id', ['pets.*']));
48+
}
49+
50+
/** @test */
51+
public function test_get_columns(): void
52+
{
53+
$query = Pet::query();
54+
$this->assertIsNotArray(ColumnUtilities::columnsFromBuilder($query));
55+
$query->select('id', 'name');
56+
$this->assertIsArray(ColumnUtilities::columnsFromBuilder($query));
57+
$this->assertCount(2, ColumnUtilities::columnsFromBuilder($query));
58+
$this->assertEquals(['id', 'name',], ColumnUtilities::columnsFromBuilder($query));
59+
}
60+
61+
/** @test */
62+
public function test_map(): void
63+
{
64+
// simple match
65+
$query = Pet::query()->select('id');
66+
$this->assertEquals('id', ColumnUtilities::mapToSelected('id', $query));
67+
68+
// wildcard match
69+
$query = Pet::query()->select('*');
70+
$this->assertEquals('id', ColumnUtilities::mapToSelected('id', $query));
71+
72+
// alias match
73+
$query = Pet::query()->select('pets.id');
74+
$this->assertEquals('pets.id', ColumnUtilities::mapToSelected('pets.id', $query));
75+
76+
// alias wildcard match
77+
$query = Pet::query()->select('pets.*');
78+
$this->assertEquals('pets.id', ColumnUtilities::mapToSelected('pets.id', $query));
79+
80+
// join relation wildcard match
81+
$query = Pet::query()->select('breeds.*')->join('breeds', 'pets.breeds_id', '=', 'breeds.id');
82+
$this->assertEquals('breeds.id', ColumnUtilities::mapToSelected('breeds.id', $query));
83+
// using relation only
84+
$this->assertEquals('breeds.id', ColumnUtilities::mapToSelected('breed.id', $query));
85+
}
86+
}

tests/Models/Pet.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
* @property string $last_visit
1212
* @property int $species_id
1313
* @property int $breed_id
14+
* @property Species $species
15+
* @property Breed $breed
1416
*/
1517
class Pet extends Model
1618
{
@@ -48,17 +50,17 @@ class Pet extends Model
4850
];
4951

5052
/**
51-
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Species[]
53+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Species
5254
*/
5355
public function species()
5456
{
5557
return $this->belongsTo(Species::class);
5658
}
5759

5860
/**
59-
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Breed[]
61+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Breed
6062
*/
61-
public function breeds()
63+
public function breed()
6264
{
6365
return $this->belongsTo(Breed::class);
6466
}

0 commit comments

Comments
 (0)