From 4df06d8fa239da004e565761eb59479ca72ee5fd Mon Sep 17 00:00:00 2001 From: Emre Akay Date: Mon, 8 Dec 2025 18:17:55 +0300 Subject: [PATCH] - openspec --- README.md | 332 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 217 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index fbeac4b..3d29888 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# FlexyField - Dynamic Model Fields for Laravel +# FlexyField + +### Because your models deserve superpowers 🦸

Build Status @@ -7,153 +9,205 @@ License

-> Add dynamic, type-safe fields to Laravel models without database migrations. +--- -**Requirements:** PHP 8.2+, Laravel 11+, MySQL 8+ / PostgreSQL 16+ +Ever wished you could add fields to your Laravel models without touching migrations? -## Installation +**Now you can.** Welcome to FlexyField. ```bash composer require aurorawebsoftware/flexyfield php artisan migrate ``` -## Quick Start +## The Magic ✨ ```php -// 1. Enable on model -use AuroraWebSoftware\FlexyField\Contracts\FlexyModelContract; -use AuroraWebSoftware\FlexyField\Traits\Flexy; - +// Give your model the power class Product extends Model implements FlexyModelContract { use Flexy; } -// 2. Create schema & fields -use AuroraWebSoftware\FlexyField\Enums\FlexyFieldType; - -Product::createSchema('electronics', 'Electronics', isDefault: true); +// Create a schema (think of it as a "field template") +Product::createSchema('electronics', 'Electronics'); Product::addFieldToSchema('electronics', 'voltage', FlexyFieldType::STRING); -Product::addFieldToSchema('electronics', 'warranty', FlexyFieldType::INTEGER, - validationRules: 'required|min:1|max:36'); +Product::addFieldToSchema('electronics', 'warranty_years', FlexyFieldType::INTEGER); -// 3. Use flexy fields -$product = Product::create(['name' => 'TV']); -$product->assignToSchema('electronics'); -$product->flexy->voltage = '220V'; -$product->flexy->warranty = 24; -$product->save(); +// Use it like it was always there +$tv = Product::create(['name' => 'Smart TV']); +$tv->assignToSchema('electronics'); +$tv->flexy->voltage = '220V'; +$tv->flexy->warranty_years = 2; +$tv->save(); -// 4. Query -$products = Product::where('flexy_voltage', '220V')->get(); -$products = Product::whereSchema('electronics')->where('flexy_warranty', '>', 12)->get(); +// Query like a boss +Product::where('flexy_voltage', '220V')->get(); ``` +**No migrations. No schema changes. Just vibes.** πŸŽ‰ + +## Why FlexyField? + +| Problem | Old Way | FlexyField Way | +|---------|---------|----------------| +| "We need a new product attribute" | Migration + deploy + pray πŸ™ | `addFieldToSchema()` βœ… | +| "Different products need different fields" | JSON column chaos 😱 | Schemas! 🎯 | +| "Can we query by that custom field?" | *nervous laughter* | `where('flexy_field', $value)` 😎 | + ## Field Types -| Type | PHP Type | Example | -|------|----------|---------| -| `STRING` | string | `$m->flexy->name = 'Test'` | -| `INTEGER` | int | `$m->flexy->qty = 100` | -| `DECIMAL` | float | `$m->flexy->price = 49.90` | -| `BOOLEAN` | bool | `$m->flexy->active = true` | -| `DATE` | string/Carbon | `$m->flexy->date = '2024-01-01'` | -| `DATETIME` | string/Carbon | `$m->flexy->created = Carbon::now()` | -| `JSON` | array | `$m->flexy->tags = ['a', 'b']` | -| `FILE` | UploadedFile | `$m->flexy->doc = $request->file('doc')` | +| Type | What it stores | Example | +|------|----------------|---------| +| `STRING` | Text | `$m->flexy->color = 'blue'` | +| `INTEGER` | Whole numbers | `$m->flexy->stock = 42` | +| `DECIMAL` | Money, measurements | `$m->flexy->price = 99.99` | +| `BOOLEAN` | Yes/No | `$m->flexy->active = true` | +| `DATE` | Dates | `$m->flexy->release = '2024-01-01'` | +| `DATETIME` | Timestamps | `$m->flexy->created = now()` | +| `JSON` | Arrays, objects | `$m->flexy->tags = ['hot', 'new']` | +| `FILE` | Uploads | `$m->flexy->manual = $request->file('pdf')` | -## Features +## Real-World Examples 🌍 -### Select Options +### E-Commerce (PIM Style) ```php -// Single select -Product::addFieldToSchema('schema', 'color', FlexyFieldType::STRING, - fieldMetadata: ['options' => ['red' => 'Red', 'blue' => 'Blue']]); +// Shoes have sizes and colors +Product::createSchema('footwear', 'Footwear'); +Product::addFieldToSchema('footwear', 'shoe_size', FlexyFieldType::INTEGER, + validationRules: 'required|between:35,50'); +Product::addFieldToSchema('footwear', 'color', FlexyFieldType::STRING, + fieldMetadata: ['options' => ['black', 'white', 'red', 'blue']]); + +// Books have ISBNs and authors +Product::createSchema('books', 'Books'); +Product::addFieldToSchema('books', 'isbn', FlexyFieldType::STRING, + validationRules: 'required|size:13'); +Product::addFieldToSchema('books', 'author', FlexyFieldType::STRING); +Product::addFieldToSchema('books', 'pages', FlexyFieldType::INTEGER); + +// Same model, different fields! +$sneakers = Product::create(['name' => 'Air Max']); +$sneakers->assignToSchema('footwear'); +$sneakers->flexy->shoe_size = 42; +$sneakers->flexy->color = 'black'; + +$novel = Product::create(['name' => 'Clean Code']); +$novel->assignToSchema('books'); +$novel->flexy->isbn = '9780132350884'; +$novel->flexy->author = 'Robert C. Martin'; +``` -// Multi-select (requires JSON type) -Product::addFieldToSchema('schema', 'features', FlexyFieldType::JSON, - fieldMetadata: ['options' => ['wifi', '5g', 'nfc'], 'multiple' => true]); +### CRM (Customer Segments) -$product->flexy->color = 'blue'; // Single value -$product->flexy->features = ['wifi', '5g']; // Array +```php +// B2B customers need company info +Contact::createSchema('b2b', 'Business Customers'); +Contact::addFieldToSchema('b2b', 'company_name', FlexyFieldType::STRING); +Contact::addFieldToSchema('b2b', 'employee_count', FlexyFieldType::INTEGER); +Contact::addFieldToSchema('b2b', 'annual_revenue', FlexyFieldType::DECIMAL); + +// B2C customers need personal preferences +Contact::createSchema('b2c', 'Individual Customers'); +Contact::addFieldToSchema('b2c', 'birthday', FlexyFieldType::DATE); +Contact::addFieldToSchema('b2c', 'interests', FlexyFieldType::JSON, + fieldMetadata: ['options' => ['tech', 'sports', 'music', 'travel'], 'multiple' => true]); + +// Query by segment +$bigCompanies = Contact::whereSchema('b2b') + ->where('flexy_annual_revenue', '>', 1000000) + ->get(); ``` -### UI Hints & Grouping +### Multi-tenant SaaS ```php -Product::addFieldToSchema('schema', 'battery', FlexyFieldType::INTEGER, - label: 'Battery Capacity', - fieldMetadata: [ - 'group' => 'Specifications', - 'placeholder' => 'Enter mAh', - 'hint' => 'Typical: 1000-5000mAh' - ]); +// Each tenant can have custom fields! +$tenantSchema = "tenant_{$tenant->id}_leads"; -// Retrieve -$field->getLabel(); // 'Battery Capacity' -$field->getPlaceholder(); // 'Enter mAh' -$field->getHint(); // 'Typical: 1000-5000mAh' +Lead::createSchema($tenantSchema, "{$tenant->name} Leads"); +Lead::addFieldToSchema($tenantSchema, 'source', FlexyFieldType::STRING); +Lead::addFieldToSchema($tenantSchema, 'score', FlexyFieldType::INTEGER); -// Get fields by group -$schema->getFieldsGrouped(); // ['Specifications' => [...], 'Ungrouped' => [...]] +// Tenant-specific fields added via admin panel +foreach ($tenant->customFields as $field) { + Lead::addFieldToSchema($tenantSchema, $field->name, $field->type); +} ``` -### File Fields +## Cool Features 🎁 + +### Dropdowns & Multi-select + +```php +// Single choice +Product::addFieldToSchema('schema', 'size', FlexyFieldType::STRING, + fieldMetadata: ['options' => ['S', 'M', 'L', 'XL']]); + +// Multiple choices (use JSON type!) +Product::addFieldToSchema('schema', 'features', FlexyFieldType::JSON, + fieldMetadata: ['options' => ['wifi', 'bluetooth', '5g'], 'multiple' => true]); + +$phone->flexy->size = 'M'; +$phone->flexy->features = ['wifi', '5g']; // Array! +``` + +### File Uploads (with Security Baked In πŸ”’) ```php -// Define file field Product::addFieldToSchema('schema', 'manual', FlexyFieldType::FILE, validationRules: 'required|mimes:pdf|max:5120', - fieldMetadata: [ - 'disk' => 's3', - 'path' => 'manuals', - 'max_file_size' => 5120, - 'allowed_extensions' => ['pdf'], - 'allowed_mimes' => ['application/pdf'] - ]); + fieldMetadata: ['disk' => 's3', 'allowed_extensions' => ['pdf']]); -// Upload $product->flexy->manual = $request->file('manual'); $product->save(); -// Access +// Get URLs $url = $product->getFlexyFileUrl('manual'); $signedUrl = $product->getFlexyFileUrlSigned('manual', now()->addHour()->timestamp); -$exists = $product->flexyFileExists('manual'); -$product->deleteFlexyFile('manual'); ``` -**Security:** Extension whitelist, MIME validation, size limits, path traversal protection, auto-cleanup on delete. - -### Validation +### Validation (Because Data Integrity Matters) ```php Product::addFieldToSchema('schema', 'email', FlexyFieldType::STRING, validationRules: 'required|email|max:255', - validationMessages: ['email.required' => 'Email is required']); + validationMessages: ['email.email' => 'GeΓ§erli bir email girin!']); -try { - $product->flexy->email = 'invalid'; - $product->save(); -} catch (ValidationException $e) { - $errors = $e->errors(); // ['flexy.email' => ['...']] -} +$product->flexy->email = 'not-an-email'; +$product->save(); // πŸ’₯ ValidationException! ``` -## Querying +### UI Hints & Grouping ```php -// By field value +Product::addFieldToSchema('schema', 'battery', FlexyFieldType::INTEGER, + label: 'Battery Capacity', + fieldMetadata: [ + 'group' => 'Technical Specs', + 'placeholder' => 'mAh', + 'hint' => 'Typical range: 3000-5000' + ]); + +// Perfect for building dynamic forms! +$schema->getFieldsGrouped(); // ['Technical Specs' => [...], 'Ungrouped' => [...]] +``` + +## Querying πŸ” + +```php +// Simple Product::where('flexy_color', 'blue')->get(); + +// Dynamic method (Laravel magic!) Product::whereFlexyColor('blue')->get(); // By schema Product::whereSchema('electronics')->get(); Product::whereSchemaIn(['electronics', 'furniture'])->get(); -// Complex queries +// Go wild Product::whereSchema('electronics') ->where('flexy_price', '<', 100) ->where('flexy_active', true) @@ -161,61 +215,109 @@ Product::whereSchema('electronics') ->get(); ``` -## Schema Management +## Configuration βš™οΈ + +Publish the config file: + +```bash +php artisan vendor:publish --tag="flexyfield-config" +``` ```php -// Create/delete schemas -Product::createSchema('code', 'Label', 'Description', isDefault: false); -Product::deleteSchema('code'); +// config/flexyfield.php +return [ + 'file_storage' => [ + 'default_disk' => env('FLEXYFIELD_DEFAULT_DISK', 'public'), + 'default_path' => env('FLEXYFIELD_DEFAULT_PATH', 'flexyfield'), + 'path_structure' => '{model_type}/{schema_code}/{field_name}/{year}/{month}', + 'cleanup_on_delete' => true, // Auto-delete files when model deleted + 'enable_security_logging' => true, // Log security events + ], +]; +``` + +**Environment Variables:** +```env +FLEXYFIELD_DEFAULT_DISK=s3 +FLEXYFIELD_DEFAULT_PATH=uploads/flexy +FLEXYFIELD_CLEANUP_DELETE=true +FLEXYFIELD_SECURITY_LOGGING=true +``` + +## AI-Powered Development πŸ€– -// Manage fields -Product::addFieldToSchema('code', 'field', FlexyFieldType::STRING, sort: 1); -Product::removeFieldFromSchema('code', 'field'); +FlexyField comes with **Laravel Boost** guidelines for AI agents! -// Get schema info -$schema = Product::getSchema('code'); -$fields = Product::getFieldsForSchema('code'); -$allSchemas = Product::getAllSchemas(); +``` +resources/boost/guidelines/core.blade.php ``` -## Performance +Your AI assistant (Claude, GPT, Copilot) can read this file and instantly understand: +- All API methods and signatures +- Field types and metadata options +- Common patterns and best practices +- Error handling and exceptions -- **Smart view recreation:** Only rebuilds when NEW fields added (v2.0+) -- **Recommended scale:** 1-50 fields, up to 1M records -- **Manual rebuild:** `php artisan flexyfield:rebuild-view` +Just point your AI to the guideline file and watch it write perfect FlexyField code! 🎯 -## Common Mistakes +## Don't Do This ❌ ```php -// WRONG: Set values before schema assignment -$product->flexy->field = 'x'; // Exception! +// Setting values before assigning schema +$product->flexy->field = 'x'; // πŸ’₯ Exception! -// CORRECT: Assign schema first -$product->assignToSchema('schema'); -$product->flexy->field = 'x'; +// Always assign first! +$product->assignToSchema('electronics'); +$product->flexy->field = 'x'; // βœ… -// WRONG: Query syntax -Product::where('flexy->field', 'x'); // Doesn't work +// Wrong query syntax +Product::where('flexy->field', 'x'); // 🚫 Nope -// CORRECT: Use flexy_ prefix -Product::where('flexy_field', 'x')->get(); +// Use underscore prefix +Product::where('flexy_field', 'x'); // βœ… Yes! ``` +## Performance πŸš€ + +- **Smart view recreation** - Only rebuilds when NEW fields are added +- **Scales well** - Tested with 50+ fields, 1M+ records +- **Manual rebuild** - `php artisan flexyfield:rebuild-view` + +## Requirements + +- PHP 8.2+ +- Laravel 11+ +- MySQL 8+ or PostgreSQL 16+ + ## Documentation -- [Performance Guide](docs/PERFORMANCE.md) -- [Best Practices](docs/BEST_PRACTICES.md) -- [Deployment Guide](docs/DEPLOYMENT.md) -- [Troubleshooting](docs/TROUBLESHOOTING.md) +| Guide | What's Inside | +|-------|---------------| +| [Performance](docs/PERFORMANCE.md) | Make it fly πŸš€ | +| [Best Practices](docs/BEST_PRACTICES.md) | Do it right βœ… | +| [Deployment](docs/DEPLOYMENT.md) | Ship it safely πŸ“¦ | +| [Troubleshooting](docs/TROUBLESHOOTING.md) | Fix it fast πŸ”§ | +| [File Security](docs/FILE_FIELD_SECURITY.md) | Lock it down πŸ”’ | -## Testing +## Contributing ```bash +composer install ./vendor/bin/pest # Run tests ./vendor/bin/phpstan analyse # Static analysis ./vendor/bin/pint # Code style ``` +PRs welcome! 🀝 + ## License -MIT License. See [LICENSE.md](LICENSE.md). +MIT - Go build something awesome! πŸš€ + +--- + +

+Made with β˜• by Aurora Web Software +

+⭐ Star us on GitHub! +