Installation:
composer require tivie/htaccess-parser
Add to composer.json under require:
"tivie/htaccess-parser": "^0.4.0"
First Use Case:
Parse an existing .htaccess file:
use Tivie\HtaccessParser\Parser;
$parser = new Parser();
$htaccess = $parser->parse(new \SplFileObject(public_path('.htaccess')));
Key Classes to Know:
Parser: Core class for parsing .htaccess files.HtaccessContainer: Stores parsed tokens (directives, blocks, etc.).TokenInterface: Base interface for all token types (directives, blocks, comments)..htaccess GenerationUseful for Laravel deployments or multi-environment configs:
$htaccess = new \Tivie\HtaccessParser\HtaccessContainer();
$htaccess[] = new \Tivie\HtaccessParser\Token\Directive('RewriteEngine', 'On');
$htaccess[] = new \Tivie\HtaccessParser\Token\Directive('RewriteRule', '^$', 'public/index.php');
file_put_contents(public_path('.htaccess'), (string)$htaccess);
Add environment-specific rules (e.g., staging vs. production):
$htaccess = $parser->parse(new \SplFileObject(public_path('.htaccess')));
if (app()->environment('staging')) {
$block = new \Tivie\HtaccessParser\Token\Block('IfModule', 'mod_rewrite.c');
$block->addToken(new \Tivie\HtaccessParser\Token\Directive('RewriteRule', '^$', 'staging/index.php'));
$htaccess->insertAt(0, $block);
}
file_put_contents(public_path('.htaccess'), (string)$htaccess);
Remove or modify unsafe directives (e.g., AllowOverride):
$htaccess = $parser->parse(new \SplFileObject(public_path('.htaccess')));
foreach ($htaccess as $token) {
if ($token instanceof \Tivie\HtaccessParser\Token\Directive && $token->getName() === 'AllowOverride') {
$token->setArguments('None'); // Force strict permissions
}
}
file_put_contents(public_path('.htaccess'), (string)$htaccess);
Update directives across multiple files (e.g., for Laravel migrations):
$htaccess = $parser->parse(new \SplFileObject(public_path('.htaccess')));
$htaccess->search('mod_rewrite')->setName('mod_rewrite'); // Case-insensitive fix
// Replace all occurrences of `Options -MultiViews` with `Options +FollowSymLinks`
foreach ($htaccess as $token) {
if ($token instanceof \Tivie\HtaccessParser\Token\Directive &&
$token->getName() === 'Options' &&
in_array('-MultiViews', $token->getArguments())) {
$token->setArguments('+FollowSymLinks');
}
}
Bootstrapping .htaccess manipulation in a service provider:
public function boot()
{
$parser = new Parser();
$htaccess = $parser->parse(new \SplFileObject(public_path('.htaccess')));
// Add Laravel-specific rules
$htaccess[] = new \Tivie\HtaccessParser\Token\Directive('DirectoryIndex', 'index.php');
$htaccess[] = new \Tivie\HtaccessParser\Token\Block('FilesMatch', '\.php$')
->addToken(new \Tivie\HtaccessParser\Token\Directive('SetHandler', 'proxy:unix:/run/php/php8.2-fpm.sock'));
file_put_contents(public_path('.htaccess'), (string)$htaccess);
}
Environment-Aware Parsing: Use Laravel’s config to toggle parsing behavior:
$parser = new Parser();
$parser->ignoreComments(config('htaccess.ignore_comments', false));
$parser->ignoreWhiteLines(config('htaccess.ignore_whitespace', true));
Artisan Commands:
Create a custom command for .htaccess management:
use Illuminate\Console\Command;
use Tivie\HtaccessParser\Parser;
class HtaccessCommand extends Command
{
protected $signature = 'htaccess:generate {--env=production}';
protected $description = 'Generate optimized .htaccess';
public function handle()
{
$parser = new Parser();
$htaccess = $parser->parse(new \SplFileObject(public_path('.htaccess')));
// Apply env-specific rules
if ($this->option('env') === 'staging') {
$this->addStagingRules($htaccess);
}
file_put_contents(public_path('.htaccess'), (string)$htaccess);
$this->info('Regenerated .htaccess');
}
}
Package Development:
Bundle .htaccess templates and merge them dynamically:
public function install()
{
$template = new \Tivie\HtaccessParser\HtaccessContainer();
$template[] = new \Tivie\HtaccessParser\Token\Directive('RewriteEngine', 'On');
$template[] = new \Tivie\HtaccessParser\Token\Directive('RewriteBase', '/');
$userHtaccess = $this->parser->parse(new \SplFileObject(public_path('.htaccess')));
$merged = array_merge($template, $userHtaccess);
file_put_contents(public_path('.htaccess'), (string)new \Tivie\HtaccessParser\HtaccessContainer($merged));
}
Case Sensitivity in Blocks:
Block names (e.g., <IfModule>) are case-insensitive in .htaccess, but the parser treats them as case-sensitive by default. Use:
$block = new \Tivie\HtaccessParser\Token\Block('ifmodule', 'mod_rewrite.c');
or override the getName() method in custom blocks.
Multiline Directives:
Directives ending with \ (e.g., RewriteCond %{HTTP_HOST} ^example\.com) may not parse correctly. Use useArrays(true) to debug:
$parser->useArrays(true);
$tokens = $parser->parse($file);
print_r($tokens); // Inspect raw structure
File Pointer Issues: The parser rewinds the file pointer by default. Disable with:
$parser->rewindFile(false);
Useful when parsing large files or streams.
Token Modification Side Effects:
Modifying a Directive or Block token’s arguments does not validate the syntax. Always test the output:
$token = new \Tivie\HtaccessParser\Token\Directive('InvalidDirective', 'foo');
echo (string)$token; // May output malformed syntax
Array vs. Object Tokens:
When using useArrays(true), tokens become associative arrays. Access properties like:
$token['name']; // Instead of $token->getName()
But lose type safety and IDE autocompletion.
Inspect Tokens: Dump the token structure for debugging:
$parser->useArrays(true);
$tokens = $parser->parse(new \SplFileObject(public_path('.htaccess')));
dd($tokens); // Debug raw token array
Validate Output:
After modifications, validate the generated .htaccess:
$output = (string)$htaccess;
$this->assertStringContainsString('RewriteEngine On', $output);
Handle Exceptions: Wrap parsing in a try-catch:
try {
$htaccess = $parser->parse($file);
} catch (\RuntimeException $e) {
Log::error("HTAccess parse error: " . $e->getMessage());
throw new \Exception("Failed to parse .htaccess");
}
Custom Token Types:
Extend TokenInterface for domain-specific tokens:
class LaravelDirective implements \Tivie\HtaccessParser\Token\TokenInterface
{
public function __toString()
{
return "# Laravel: " . $this->getName() . " " . implode(' ', $this->getArguments());
}
}
Parser Hooks: Override parser methods to add preprocessing:
How can I help you explore Laravel packages today?