funstaff/tika-bundle
A Symfony bundle that integrates Apache Tika to extract text and metadata from documents such as PDFs, Office files, and images. It provides service configuration and a simple way to add content extraction to PHP applications, useful for search indexing and document processing workflows.
Reality check before you composer require anything: funstaff/tika-bundle is a Symfony bundle — it ships *Bundle classes, DependencyInjection/Extension config, and service definitions compiled into a Symfony kernel. Laravel has no bundle concept, no container extension, no config/packages/*.yaml. If you install it in a Laravel app, the only thing you can actually use is the underlying Tika client it wraps (typically vaites/php-apache-tika). The bundle layer is dead weight. Treat this repo as prior art, not a dependency.
What to look at first: the bundle's Resources/config/services.yml and the underlying client API. That's where the only real knowledge lives — how to configure the Tika binary path, how to configure JAR options (e.g. -Xmx, -t), and the two calls you'll actually make: extract text, extract metadata.
Prerequisites (the part the bundle doesn't solve for you):
tika-app.jar on disk, or a running tika-server container (recommended).vaites/php-apache-tika (the PHP client the bundle wraps), or nothing at all — just Http::timeout(30)->post('http://tika:9998/tika', ...) against a tika-server container.First use case — get it working end-to-end in a day:
docker run -p 9998:9998 apache/tika:latest (pin the tag — see Gotchas).TextExtractor service in app/Services/ that hits PUT /tika (plain text) and PUT /meta (metadata) via Http::client(). ~50–80 lines, all yours.ExtractDocumentText queued on a dedicated extraction queue.If you only need the spike, skip the jar and shell-out entirely. The container + HTTP client path is smaller, safer, and testable. That spike is your production design.
Interface-first, so you can swap engines later.
interface TextExtractor {
public function text(string $path): string;
public function metadata(string $path): array;
public function mimeType(string $path): string;
}
Bind TikaTextExtractor in a DocumentServiceProvider. This is the one thing worth copying from the bundle: it exists to produce exactly this seam in a DI container.
Config, not hardcoded. Publish a config/documents.php with tika.url, tika.timeout, tika.max_bytes, tika.max_pages. The bundle exposes these as bundle config; in Laravel they're a config file and env vars. Same idea, your lifecycle.
Always extract in a queue job. Extraction is multi-second on real documents. Never do it inline in a controller.
class ExtractDocumentText implements ShouldQueue {
public $queue = 'extraction';
public $timeout = 120;
public $tries = 3;
public $backoff = [5, 30, 120];
// handle(): $text = $this->extractor->text($this->storedPath());
}
Detect MIME server-side. The whole point of putting a document through Tika is that $_FILES['f']['type'] and the extension are attacker-controlled. Call mimeType() and gate on that (allowlist PDF/DOCX/XLSX/PPTX/HTML), then extract. This is often the first integration the developer actually needs — validation, not text.
Store extracted text; don't re-extract. Add documents.extracted_text (longtext) + documents.metadata (json) + documents.extracted_at. The Tika call is the expensive part; make it idempotent and re-runnable.
RAG / search pipeline shape:
upload → MIME gate → queue job → tika-server → text → chunk → embed → vector store
└→ metadata → DB (author, pages, created)
Metadata harvesting pattern. PUT /meta returns XHTML; parse with DOMDocument + XPath into a flat array (Content-Type, dc:creator, meta:page-count, pdf:PDFVersion). Keep it normalized — every format reports different keys.
Sync vs async split:
Fail-open vs fail-closed. Decide per feature. Search indexing can tolerate a failed extraction (log + retry). Compliance/KYC intake should block the upload. Encode this in the job/controller, not in the extractor.
symfony/framework-bundle, old PHP pins) will fight Composer resolution. --ignore-platform-reqs is a symptom, not a fix. Use vaites/php-apache-tika or plain HTTP.tika-server for anything user-facing, and health-check the container independently of the PHP app.-Xmx explicitly, cap upload size (e.g. 20–50MB), cap page count, and set a hard per-request timeout. Without these, one bad file takes out a worker.Content-Length: 0 is not success — it's a parser that gave up. Assert non-empty text on formats you expect to have text; log the raw length and Content-Type on every call.Normalizer::normalize, strip control chars) before indexing or chunking for embeddings.application/octet-stream if Tika can't sniff them, and Office XML wrappers can differ by generator. Keep a small allowlist of accepted types and a fallback message — don't let octet-stream silently reach a "validated" state.apache/tika:latest will rotate under you, change parser behavior, and reintroduce CVEs. Pin to a specific minor (e.g. apache/tika:2.9.2), patch deliberately, and track Tika parser CVEs yourself — the bundle has zero security-patch cadence.vaites/php-apache-tika if that's your chosen client — pin it, wrap it behind your interface, and be ready to replace it.(file hash, format-detected, bytes-in, bytes-out, ms, http-status) on every call. When a user says "search can't find my document," you want the answer in one query, not a re-run.How can I help you explore Laravel packages today?