@@ -507,6 +507,196 @@ field('author')->matchable(function ($request, $query, $value) {
507507}),
508508```
509509
510+ ## Searchable
511+
512+ Fields can be made searchable, enabling them to respond to global search queries. This provides field-level control over search behavior while maintaining the simplicity of the global search API.
513+
514+ ### Making Fields Searchable
515+
516+ To make a field searchable, chain the ` searchable() ` method:
517+
518+ ``` php
519+ public function fields(RestifyRequest $request)
520+ {
521+ return [
522+ field('title')->searchable(),
523+ field('description')->searchable(),
524+ field('email')->searchable(),
525+ ];
526+ }
527+ ```
528+
529+ The ` searchable() ` method uses a unified flexible signature that accepts multiple arguments and works consistently across all field types:
530+
531+ ``` php
532+ // Basic usage
533+ field('title')->searchable(),
534+
535+ // Custom column
536+ field('name')->searchable('users.full_name'),
537+
538+ // With optional type
539+ field('price')->searchable('products.price', 'numeric'),
540+
541+ // Multiple attributes (especially useful for relationship fields like BelongsTo)
542+ BelongsTo::make('author')->searchable('name', 'email', 'username'),
543+
544+ // Array of attributes (legacy support)
545+ BelongsTo::make('editor')->searchable(['users.name', 'users.email']),
546+
547+ // Closure/callback
548+ field('content')->searchable(function ($request, $query, $value) {
549+ // Custom search logic
550+ }),
551+
552+ // Custom filter instance
553+ field('complex_search')->searchable(new CustomSearchFilter()),
554+
555+ // Invokable class
556+ field('tags')->searchable(new TagSearchHandler()),
557+ ```
558+
559+ ### Unified Method Signatures
560+
561+ All searchable-related methods now use consistent signatures across regular fields and relationship fields:
562+
563+ ``` php
564+ // All field types use the same signatures:
565+ searchable(...$attributes) // Flexible variadic signature
566+ isSearchable(?RestifyRequest $request = null) // Optional request parameter
567+ getSearchColumn(?RestifyRequest $request = null) // Optional request parameter
568+
569+ // BelongsTo also provides relationship-specific method:
570+ getSearchables(): array // Returns multiple searchable attributes
571+ ```
572+
573+ ### Using Searchable Fields
574+
575+ Searchable fields respond to the standard ` search ` query parameter:
576+
577+ ``` http
578+ GET /api/restify/posts?search=laravel
579+ ```
580+
581+ This will search across all searchable fields for the term "laravel".
582+
583+ ### Advanced Searchable Configuration
584+
585+ #### Basic Usage (No Arguments)
586+
587+ When called without arguments, ` searchable() ` applies standard search behavior using the field's attribute:
588+
589+ ``` php
590+ field('title')->searchable(), // Searches the 'title' column with LIKE operator
591+ ```
592+
593+ #### Custom Column
594+
595+ Specify a different database column for searching:
596+
597+ ``` php
598+ field('author_name')->searchable('users.name'), // Search in users.name column
599+ ```
600+
601+ You can also specify multiple attributes for relationship fields (like BelongsTo):
602+
603+ ``` php
604+ BelongsTo::make('author', UserRepository::class)->searchable('name', 'email'),
605+ ```
606+
607+ #### Closure-based Searching
608+
609+ For custom search logic, pass a closure that receives the request, query builder, and search value:
610+
611+ ``` php
612+ field('content')->searchable(function ($request, $query, $value) {
613+ $query->where('title', 'LIKE', "%{$value}%")
614+ ->orWhere('description', 'LIKE', "%{$value}%");
615+ }),
616+ ```
617+
618+ #### Custom SearchableFilter Classes
619+
620+ Create dedicated filter classes for complex search logic:
621+
622+ ``` php
623+ field('complex_search')->searchable(new CustomContentSearchFilter),
624+ ```
625+
626+ Where ` CustomContentSearchFilter ` extends ` SearchableFilter ` :
627+
628+ ``` php
629+ use Binaryk\LaravelRestify\Filters\SearchableFilter;
630+ use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
631+
632+ class CustomContentSearchFilter extends SearchableFilter
633+ {
634+ public function filter(RestifyRequest $request, $query, $value)
635+ {
636+ return $query->where(function ($q) use ($value) {
637+ $q->where('title', 'LIKE', "%{$value}%")
638+ ->orWhere('description', 'LIKE', "%{$value}%")
639+ ->orWhere('tags', 'LIKE', "%{$value}%");
640+ });
641+ }
642+ }
643+ ```
644+
645+ #### Invokable Classes
646+
647+ For reusable search logic, use invokable classes:
648+
649+ ``` php
650+ field('tags')->searchable(new TagSearchFilter),
651+ ```
652+
653+ ``` php
654+ class TagSearchFilter
655+ {
656+ public function __invoke($request, $query, $value)
657+ {
658+ $tags = explode(',', $value);
659+ $query->whereHas('tags', function ($q) use ($tags) {
660+ $q->whereIn('name', $tags);
661+ });
662+ }
663+ }
664+ ```
665+
666+ #### Practical Examples
667+
668+ ** Full-text Search:**
669+ ``` php
670+ field('content')->searchable(function ($request, $query, $value) {
671+ $query->whereFullText(['title', 'description'], $value);
672+ }),
673+ ```
674+
675+ ** Multi-field Search:**
676+ ``` php
677+ field('user_search')->searchable(function ($request, $query, $value) {
678+ $query->where('name', 'LIKE', "%{$value}%")
679+ ->orWhere('email', 'LIKE', "%{$value}%")
680+ ->orWhere('phone', 'LIKE', "%{$value}%");
681+ }),
682+ ```
683+
684+ ** Relationship Search:**
685+ ``` php
686+ field('author')->searchable(function ($request, $query, $value) {
687+ $query->whereHas('author', function ($q) use ($value) {
688+ $q->where('name', 'like', "%{$value}%");
689+ });
690+ }),
691+ ```
692+
693+ ** JSON Search:**
694+ ``` php
695+ field('metadata')->searchable(function ($request, $query, $value) {
696+ $query->whereJsonContains('metadata->tags', $value);
697+ }),
698+ ```
699+
510700## Validation
511701
512702There is a golden rule that says - catch the exception as soon as possible on its request way.
0 commit comments