Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sort Bundle Laravel Package

cannibal/sort-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require cannibal/sort-bundle
    

    Add to config/app.php under providers:

    Cannibal\SortBundle\SortBundle::class,
    
  2. Basic Usage Enable sorting for a controller method by annotating the action with @Sortable:

    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Cannibal\SortBundle\Annotation\Sortable;
    
    class ProductController extends Controller
    {
        /**
         * @Route("/products", name="products_list")
         * @Sortable
         */
        public function listAction()
        {
            $products = $this->getDoctrine()->getRepository(Product::class)->findAll();
            return $this->renderProducts($products);
        }
    }
    
  3. First Use Case

    • Append ?sort=price&direction=desc to the URI to sort products by price in descending order.
    • The bundle automatically handles the sorting logic and passes the sorted collection to your controller.

Implementation Patterns

Common Workflows

1. Sorting Collections

  • Doctrine QueryBuilder Integration Use @Sortable with a QueryBuilder to enable database-level sorting:

    public function listAction()
    {
        $qb = $this->getDoctrine()->getRepository(Product::class)->createQueryBuilder('p');
        return $this->renderProducts($qb->getQuery()->getResult());
    }
    

    The bundle will inject ORDER BY clauses dynamically.

  • Array Collections For non-DB collections (e.g., API responses), use the SortService directly:

    $sorted = $this->get('cannibal_sort.sort_service')->sort($collection, 'price', 'desc');
    

2. Custom Sorting Logic

  • Field Mappings Override default field names via configuration (config/packages/cannibal_sort.yaml):

    cannibal_sort:
        fields:
            product: [id, name, price, 'created_at:date']
    

    Here, created_at:date maps to a custom DateTime formatter.

  • Custom Sort Handlers Implement Cannibal\SortBundle\Sort\SortHandlerInterface for complex logic:

    class CustomSortHandler implements SortHandlerInterface
    {
        public function sort($collection, $field, $direction)
        {
            // Custom logic (e.g., multi-field sorting)
            return usort($collection, fn($a, $b) => $a->$field <=> $b->$field);
        }
    }
    

    Register it in services.yaml:

    services:
        App\Sort\CustomSortHandler:
            tags: ['cannibal_sort.handler']
    

3. API Integration

  • Request-Based Sorting Let clients control sorting via query params (e.g., /products?sort[]=price&sort[]=-name). The bundle supports multi-field sorting out of the box.

  • Response Formatting Combine with Symfony\Serializer to return sorted, normalized data:

    return $this->json($this->get('serializer')->serialize($sortedProducts, 'json'));
    

4. Symfony Forms

  • Sortable Fields in Forms Use the SortType field for form-based sorting preferences:
    $builder->add('sort', SortType::class, [
        'fields' => ['price', 'name'],
        'default_direction' => 'asc',
    ]);
    

Integration Tips

  1. Doctrine DQL Support Ensure your ORDER BY fields are valid DQL identifiers. Avoid raw SQL or HAVING clauses.

  2. Pagination Compatibility Pair with KnpPaginatorBundle or Symfony\Component\Pagination:

    $paginator = $this->get('knp_paginator');
    $results = $paginator->paginate(
        $sortedProducts,
        $page,
        10
    );
    
  3. Caching Cache sorted results if the underlying data changes infrequently:

    $cacheKey = 'products_sorted_' . md5(serialize($sortParams));
    return $this->get('cache')->get($cacheKey, function() use ($qb) {
        return $qb->getQuery()->getResult();
    });
    
  4. Testing Mock the SortService in PHPUnit:

    $mockSortService = $this->createMock(SortService::class);
    $mockSortService->method('sort')->willReturn($sortedData);
    $container->set('cannibal_sort.sort_service', $mockSortService);
    

Gotchas and Tips

Pitfalls

  1. Field Name Mismatches

    • Issue: Sorting fails silently if the field name doesn’t match the property (e.g., created_at vs. createdAt).
    • Fix: Use the fields config to alias properties or ensure strict naming consistency.
  2. Case Sensitivity

    • Issue: String sorting may be case-sensitive (e.g., "Apple" vs. "apple").
    • Fix: Normalize fields in the SortHandler:
      usort($collection, fn($a, $b) => strcasecmp($a->$field, $b->$field));
      
  3. Nested Objects

    • Issue: Sorting nested properties (e.g., user.name) requires custom handlers.
    • Fix: Implement a NestedSortHandler:
      class NestedSortHandler implements SortHandlerInterface
      {
          public function sort($collection, $field, $direction)
          {
              [$object, $property] = explode('.', $field);
              usort($collection, fn($a, $b) => $a->$object->$property <=> $b->$object->$property);
          }
      }
      
  4. Performance

    • Issue: Sorting large collections in memory is slow.
    • Fix: Push sorting to the database (Doctrine) or use usort with a custom comparator for lightweight arrays.
  5. Annotation Overhead

    • Issue: @Sortable adds complexity to controller methods.
    • Fix: Use a base controller or trait:
      abstract class SortableController extends Controller
      {
          use SortableTrait;
      }
      

Debugging Tips

  1. Enable Verbose Logging Add to config/packages/monolog.yaml:

    handlers:
        cannibal_sort:
            type: stream
            path: "%kernel.logs_dir%/sort.log"
            level: debug
            channels: ["cannibal_sort"]
    

    Then log sort operations in your SortHandler:

    $this->logger->debug('Sorting by', ['field' => $field, 'direction' => $direction]);
    
  2. Check Request Params Dump the sort and direction params in your controller to verify input:

    dump($this->get('request')->query->all());
    
  3. Validate Field Existence Ensure fields exist on the object to avoid Undefined property errors:

    if (!property_exists($item, $field)) {
        throw new \InvalidArgumentException("Field '$field' not found.");
    }
    

Extension Points

  1. Custom Sort Directions Extend the direction parameter to support custom logic (e.g., natural, reverse):

    if ($direction === 'natural') {
        usort($collection, 'strnatcasecmp');
    }
    
  2. Dynamic Field Whitelisting Restrict sortable fields per entity via a service:

    $this->get('cannibal_sort.field_whitelist')->add('Product', ['price', 'name']);
    
  3. Event Listeners Trigger events before/after sorting:

    # config/packages/cannibal_sort.yaml
    cannibal_sort:
        events:
            pre_sort: App\EventListener\PreSortListener
            post_sort: App\EventListener\PostSortListener
    
  4. Localization Support Add locale-aware sorting for strings:

    use Symfony\Component\Intl\Locales;
    
    usort($collection, fn($a, $b) =>
        strcoll($a->$field, $b->$field, $this->getParameter('locale'))
    );
    
  5. Batch Processing For huge datasets, implement chunked sorting:

    $batchSize = 1000;
    $sorted = [];
    foreach (array_chunk($collection, $batchSize) as $chunk) {
        $sorted = array_merge($sorted, $this->sortChunk($chunk, $field, $direction));
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours