logo️ ObjectQuel
Getting Started
  • Introduction
  • Installation
  • Quick Start
Query Language
  • Query Structure
  • Range Definitions
  • Retrieving Data
  • Filtering & Parameters
  • Sorting & Pagination
  • Aggregate Functions
  • Functions
  • Searching
Core Features
  • Data Mapper Architecture
  • Entity Management & Annotations
  • Relationship Mapping
  • Cascading
  • Entity Bridge
  • Indexing System
  • Schema Management (Sculpt CLI)
Advanced Topics
  • Repositories
  • Lifecycle Callbacks
  • Hybrid Data Sources (JSON + Database)
  • Optimistic Locking
  • Single-Table Inheritance
API Reference
  • Annotations Reference
  • Core Classes
  • Query Internals
Contributing
  • How to Contribute
  • License

Searching

ObjectQuel provides three approaches for matching and searching text: wildcard patterns, regular expressions, and full-text search. All three are used within where clauses and can be combined with other filter conditions.

explanation

Wildcard Patterns

Place the pattern in single quotes and use the = operator — ObjectQuel detects the wildcard characters and converts the comparison into a pattern match automatically. Two wildcard characters are available:

  • * — matches any number of characters (including zero)
  • ? — matches exactly one character
// Prefix: can use an index — fast
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.name = 'iPhone*'
");

// Suffix: full table scan
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.name = '*Pro'
");

// Contains: full table scan
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.name = '*wireless*'
");

// Single-character wildcard: match exactly 3 characters
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.code = '???'
");

// Mixed: prefix portion may use an index
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.sku = 'ABC*-???-*XYZ'
");

// Negation: find rows that do not match a pattern
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.name != 'Test*'
");

Wildcard Performance

Pattern Example Index usage
Prefix 'iPhone*' Can use index — fast
Suffix '*Pro' Full scan
Contains '*wireless*' Full scan
Single-char '???' Full scan
Mixed 'ABC*-???-*XYZ' Prefix portion may use index

Regular Expressions

When wildcards aren't expressive enough, ObjectQuel supports regular expressions using MySQL regex syntax. Enclose the pattern in forward slashes instead of quotes:

// Match company email addresses
$results = $entityManager->executeQuery("
    range of u is App\\Entity\\UserEntity
    retrieve (u) where u.email = /^[a-zA-Z0-9._%+-]+@company\.com$/
");

Case Sensitivity

Case sensitivity behaviour depends on the MySQL version ObjectQuel detects at runtime:

  • MySQL 8.0+ — ObjectQuel uses REGEXP_LIKE(), which supports inline flags after the closing slash. Append i for case-insensitive matching (/pattern/i) or c to force case-sensitive matching regardless of the column's collation (/pattern/c).
  • MySQL 5.x — ObjectQuel falls back to the REGEXP operator, which does not support inline flags. Case sensitivity is determined by the column's collation: utf8mb4_unicode_ci matches case-insensitively; binary or case-sensitive collations match case-sensitively.
// Case-insensitive (MySQL 8.0+)
$results = $entityManager->executeQuery("
    range of u is App\\Entity\\UserEntity
    retrieve (u) where u.email = /^[a-z0-9._%+-]+@company\.com$/i
");

// Case-sensitive regardless of column collation (MySQL 8.0+)
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where p.code = /^[A-Z]{3}-[0-9]+$/c
");

Full-Text Search

For natural language queries — finding rows that contain certain words regardless of position — use the search() function. When a full-text index is available on the searched columns, ObjectQuel uses it for fast results. Without one, it falls back to a series of LIKE statements that return the same results but scan the entire table.

Single-Field Search

$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where search(p.description, 'bluetooth speaker')
");

Multi-Field Search

Pass multiple fields to search across several columns at once:

$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where search(p.name, p.description, 'premium quality')
");

Boolean Operators

Control which terms are required, excluded, or optional using boolean prefixes in the search string:

Operator Meaning Example
+word Must be present +bluetooth
-word Must not be present -expensive
word Optional — boosts ranking if present wireless
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p) where search(p.description, 'wireless +bluetooth -expensive')
");

Combining Search with Other Filters

search() is a regular where-clause condition and can be combined with any other filter:

$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    range of c is App\\Entity\\CategoryEntity via p.category
    retrieve (p, c.name)
    where search(p.name, p.description, '+wireless +bluetooth')
      and p.price between :min and :max
      and c.name = 'Electronics'
    sort by p.price asc
", [
    'min' => 25.00,
    'max' => 200.00
]);

Searching Across Related Entities

search() can target fields on related entities, just like any other where-clause condition:

$results = $entityManager->executeQuery("
    range of o is App\\Entity\\OrderEntity
    range of i is App\\Entity\\OrderItemEntity via o.items
    range of p is App\\Entity\\ProductEntity via i.product
    retrieve (o, p.name)
    where search(p.description, '+organic')
      and o.status = 'completed'
");
Full-text indexes are recommended. Without a full-text index on the searched columns, search() falls back to LIKE statements and requires a full table scan.

Relevance Scoring with search_score()

search_score() returns the numeric relevance score for a full-text match. It takes the same field list and search term as search(), but produces a numeric value rather than a boolean — so it belongs in the retrieve clause or a sort by clause, not a where clause. The typical pattern is to filter with search() and rank with search_score():

$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p, search_score(p.name, p.description, :term) as score)
    where search(p.name, p.description, :term)
    sort by score desc
", ['term' => 'bluetooth speaker']);

You can also expose the score as a column without sorting by it:

$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    retrieve (p.name, p.price, search_score(p.name, p.description, :term) as score)
    where search(p.name, p.description, :term)
    sort by p.price asc
", ['term' => 'wireless']);

foreach ($results as $row) {
    echo $row['p.name'] . ' (score: ' . $row['score'] . ')';
}
The field list must match between search() and search_score(). Both calls must reference the same columns. Without a full-text index on those columns, search_score() returns 0 for all rows.
logo️ ObjectQuel

Declarative query engine for PHP developers who care about clarity, control, and maintainable data access.

Resources
  • Version: 1.3.0
  • GitHub
  • Packagist
  • Documentation
  • Releases
Community
  • Contributing Guide
  • Issue Tracker
  • Sponsor on GitHub
Learn
  • Getting Started
  • Query Language
  • Architecture

© 2025-2026 Quellabs. Released under the MIT License.

Questions about ObjectQuel? floris@quellabs.com

Built with ❤️ for the PHP community