Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<?php

use Binaryk\LaravelRestify\Http\Controllers\GlobalSearchController;
use Binaryk\LaravelRestify\Http\Controllers\RepositoryDestroyController;
use Binaryk\LaravelRestify\Http\Controllers\RepositoryIndexController;
use Binaryk\LaravelRestify\Http\Controllers\RepositoryShowController;
use Binaryk\LaravelRestify\Http\Controllers\RepositoryStoreController;
use Binaryk\LaravelRestify\Http\Controllers\RepositoryUpdateController;
use Illuminate\Support\Facades\Route;

// Global Search...
Route::get('/search', '\\'.GlobalSearchController::class);

// API CRUD
Route::get('/{repository}', '\\'.RepositoryIndexController::class);
Route::post('/{repository}', '\\'.RepositoryStoreController::class);
Route::get('/{repository}/{repositoryId}', '\\'.RepositoryShowController::class);
Expand Down
2 changes: 1 addition & 1 deletion src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ protected function resolveDefaultValue(RestifyRequest $request)
* @param callable $resolveCallback
* @return $this
*/
public function resolveUsing(callable $resolveCallback)
public function resolveCallback(callable $resolveCallback)
{
$this->resolveCallback = $resolveCallback;

Expand Down
19 changes: 19 additions & 0 deletions src/Http/Controllers/GlobalSearchController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Binaryk\LaravelRestify\Http\Controllers;

use Binaryk\LaravelRestify\Http\Requests\GlobalSearchRequest;
use Binaryk\LaravelRestify\Restify;
use Binaryk\LaravelRestify\Services\Search\GlobalSearch;

class GlobalSearchController extends RepositoryController
{
public function __invoke(GlobalSearchRequest $request)
{
$results = (new GlobalSearch(
$request, Restify::globallySearchableRepositories($request)
))->get();

return $this->response()->data($results);
}
}
7 changes: 7 additions & 0 deletions src/Http/Requests/GlobalSearchRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Binaryk\LaravelRestify\Http\Requests;

class GlobalSearchRequest extends RestifyRequest
{
}
63 changes: 59 additions & 4 deletions src/Repositories/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ abstract class Repository implements RestifySearchable, JsonSerializable
*/
public static $sort;

/**
* Attribute that should be used for displaying single model.
*
* @var string
*/
public static $title = 'id';

/**
* Indicates if the repository should be globally searchable.
*
* @var bool
*/
public static $globallySearchable = true;

/**
* The number of results to display in the global search.
*
* @var int
*/
public static $globalSearchResults = 5;

/**
* Get the underlying model instance for the resource.
*
Expand Down Expand Up @@ -106,6 +127,42 @@ public static function uriKey()
return Str::plural($kebabWithoutRepository);
}

/**
* Get the label for the resource.
*
* @return string
*/
public static function label()
{
if (property_exists(static::class, 'label') && is_string(static::$label)) {
return static::$label;
}

$title = Str::title(Str::replaceLast('Repository', '', class_basename(get_called_class())));

return Str::plural($title);
}

/**
* Get the value that should be displayed to represent the repository.
*
* @return string
*/
public function title()
{
return $this->{static::$title};
}

/**
* Get the search result subtitle for the repository.
*
* @return string|null
*/
public function subtitle()
{
//
}

/**
* Get a fresh instance of the model represented by the resource.
*
Expand Down Expand Up @@ -435,10 +492,8 @@ public function index(RestifyRequest $request)
* Apply all of the query: search, match, sort, related.
* @var AbstractPaginator $paginator
*/
$paginator = RepositorySearchService::instance()->search($request, $this)->tap(function ($query) use ($request) {
// Call the local definition of the query
static::indexQuery($request, $query);
})->paginate($request->perPage ?? (static::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE));
$paginator = RepositorySearchService::instance()->search($request, $this)
->paginate($request->perPage ?? (static::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE));

$items = $paginator->getCollection()->map(function ($value) {
return static::resolveWith($value);
Expand Down
34 changes: 26 additions & 8 deletions src/Restify.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Binaryk\LaravelRestify\Events\RestifyBeforeEach;
use Binaryk\LaravelRestify\Events\RestifyStarting;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Repositories\Repository;
use Binaryk\LaravelRestify\Traits\AuthorizesRequests;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -15,6 +16,7 @@
class Restify
{
use AuthorizesRequests;

/**
* The registered repository names.
*
Expand All @@ -39,7 +41,7 @@ class Restify
/**
* Get the repository class name for a given key.
*
* @param string $key
* @param string $key
* @return string
*/
public static function repositoryForKey($key)
Expand All @@ -52,7 +54,7 @@ public static function repositoryForKey($key)
/**
* Get the repository class name for a given key.
*
* @param string $model
* @param string $model
* @return string
*/
public static function repositoryForModel($model)
Expand All @@ -69,7 +71,7 @@ public static function repositoryForModel($model)
/**
* Register the given repositories.
*
* @param array $repositories
* @param array $repositories
* @return static
*/
public static function repositories(array $repositories)
Expand All @@ -84,7 +86,7 @@ public static function repositories(array $repositories)
/**
* Register all of the repository classes in the given directory.
*
* @param string $directory
* @param string $directory
* @return void
* @throws \ReflectionException
*/
Expand Down Expand Up @@ -114,7 +116,7 @@ public static function repositoriesFrom($directory)
/**
* Get the URI path prefix utilized by Restify.
*
* @param null $plus
* @param null $plus
* @return string
*/
public static function path($plus = null)
Expand All @@ -131,7 +133,7 @@ public static function path($plus = null)
*
* This listener is added in the RestifyApplicationServiceProvider
*
* @param \Closure|string $callback
* @param \Closure|string $callback
* @return void
*/
public static function starting($callback)
Expand All @@ -140,7 +142,7 @@ public static function starting($callback)
}

/**
* @param \Closure|string $callback
* @param \Closure|string $callback
*/
public static function beforeEach($callback)
{
Expand All @@ -150,10 +152,26 @@ public static function beforeEach($callback)
/**
* Set the callback used for intercepting any request exception.
*
* @param \Closure|string $callback
* @param \Closure|string $callback
*/
public static function exceptionHandler($callback)
{
static::$renderCallback = $callback;
}

public static function globallySearchableRepositories(RestifyRequest $request)
{
return collect(static::$repositories)
->filter(fn ($repository) => $repository::authorizedToUseRepository($request))
->filter(fn ($repository) => $repository::$globallySearchable)
->sortBy(static::sortResourcesWith())
->all();
}

public static function sortResourcesWith()
{
return function ($resource) {
return $resource::label();
};
}
}
82 changes: 82 additions & 0 deletions src/Services/Search/GlobalSearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Binaryk\LaravelRestify\Services\Search;

use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Illuminate\Support\Collection;

class GlobalSearch
{
/**
* The request instance.
*
* @var RestifyRequest
*/
public $request;

/**
* The repository class names that should be searched.
*
* @var Collection
*/
public $repositories;

/**
* Create a new global search instance.
*
* @param RestifyRequest $request
* @param \Illuminate\Support\Collection repositories
* @return void
*/
public function __construct(RestifyRequest $request, $repositories)
{
$this->request = $request;
$this->repositories = $repositories;
}

/**
* Get the matching repositories.
*
* @return array
*/
public function get()
{
$formatted = [];

foreach ($this->getSearchResults() as $repository => $models) {
foreach ($models as $model) {
$instance = $repository::resolveWith($model);

$formatted[] = [
'repositoryName' => $repository::uriKey(),
'repositoryTitle' => $repository::label(),
'title' => $instance->title(),
'subTitle' => $instance->subtitle(),
'repositoryId' => $model->getKey(),
];
}
}

return $formatted;
}

/**
* Get the search results for the repositories.
*
* @return array
*/
protected function getSearchResults()
{
$results = [];

foreach ($this->repositories as $repository) {
$query = RepositorySearchService::instance()->search($this->request, $repository::resolveWith($repository::newModel()));

if (count($models = $query->limit($repository::$globalSearchResults)->get()) > 0) {
$results[$repository] = $models;
}
}

return collect($results)->sortKeys()->all();
}
}
9 changes: 7 additions & 2 deletions src/Services/Search/RepositorySearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function search(RestifyRequest $request, Repository $repository)

$query = $this->prepareMatchFields($request, $this->prepareSearchFields($request, $repository::query(), $this->fixedInput), $this->fixedInput);

return $this->prepareRelations($request, $this->prepareOrders($request, $query), $this->fixedInput);
return tap($this->prepareRelations($request, $this->prepareOrders($request, $query), $this->fixedInput), $this->applyIndexQuery($request, $repository));
}

public function prepareMatchFields(RestifyRequest $request, $query, $extra = [])
Expand Down Expand Up @@ -113,7 +113,7 @@ public function prepareSearchFields(RestifyRequest $request, $query, $extra = []
$canSearchPrimaryKey = is_numeric($search) &&
in_array($query->getModel()->getKeyType(), ['int', 'integer']) &&
($connectionType != 'pgsql' || $search <= PHP_INT_MAX) &&
in_array($query->getModel()->getKeyName(), $model::getSearchableFields());
in_array($query->getModel()->getKeyName(), $this->repository->getSearchableFields());

if ($canSearchPrimaryKey) {
$query->orWhere($query->getModel()->getQualifiedKeyName(), $search);
Expand Down Expand Up @@ -170,4 +170,9 @@ public function setOrder($query, $param)

return $query;
}

protected function applyIndexQuery(RestifyRequest $request, Repository $repository)
{
return fn ($query) => $repository::indexQuery($request, $query);
}
}
Loading