timefrontiers / php-has-errors
Standardized error handling trait for PHP classes
1.0.0
2026-04-15 11:50 UTC
Requires
- php: >=8.1
- timefrontiers/php-core: ^1.0
Requires (Dev)
- phpunit/phpunit: ^10.0
- timefrontiers/php-instance-error: ^1.0
Suggests
- timefrontiers/php-instance-error: For extracting and filtering errors by user rank
This package is auto-updated.
Last update: 2026-04-16 04:41:51 UTC
README
Standardized error handling trait for PHP classes with rank-based visibility filtering.
Installation
composer require timefrontiers/php-has-errors
Requirements
- PHP 8.1+
- (Optional)
timefrontiers/php-instance-errorfor advanced error extraction
Quick Start
use TimeFrontiers\Helper\HasErrors; class PaymentService { use HasErrors; public function charge(float $amount):bool { if ($amount <= 0) { $this->_userError('charge', 'Amount must be positive'); return false; } if (!$this->gateway->process($amount)) { $this->_systemError('charge', 'Gateway returned: ' . $this->gateway->lastError()); $this->_userError('charge', 'Payment could not be processed. Please try again.'); return false; } return true; } } // Usage $payment = new PaymentService(); if (!$payment->charge(100.00)) { // Guest sees: "Payment could not be processed. Please try again." // Developer sees: "Gateway returned: Connection timeout" + user message $errors = (new InstanceError($payment))->get('charge'); }
Error Format
Errors are stored as arrays with 5 elements:
[$min_rank, $code, $message, $file, $line]
| Index | Name | Type | Description |
|---|---|---|---|
| 0 | min_rank |
int |
Minimum AccessRank to view this error |
| 1 | code |
int |
PHP error code (e.g., 256 = E_USER_ERROR) |
| 2 | message |
string |
Human-readable error message |
| 3 | file |
string |
Source file (auto-captured) |
| 4 | line |
int |
Line number (auto-captured) |
AccessRank Enum
use TimeFrontiers\AccessRank; AccessRank::GUEST; // 0 - Public users AccessRank::USER; // 1 - Logged in users AccessRank::ANALYST; // 2 AccessRank::ADVERTISER; // 3 AccessRank::MODERATOR; // 4 - Staff (can see internal errors) AccessRank::EDITOR; // 5 AccessRank::ADMIN; // 6 AccessRank::DEVELOPER; // 7 - Technical (can see system errors) AccessRank::SUPERADMIN; // 8 - Can see debug errors AccessRank::OWNER; // 14 - Full access
Trait Methods
Core Methods
_addError()
Add an error with full control:
$this->_addError( context: 'save', message: 'Database connection failed', min_rank: AccessRank::DEVELOPER->value, code: 256 ); // File and line are auto-captured from caller
_mergeErrors()
Merge errors from another instance:
if (!$this->repository->save($entity)) { $this->_mergeErrors($this->repository, 'save', 'create'); return false; }
Helper Methods (Shortcuts)
| Method | Visible To | Use For |
|---|---|---|
_userError() |
Everyone (rank 0+) | Validation, user input, friendly messages |
_internalError() |
Staff (rank 4+) | Business logic, process failures |
_systemError() |
Developers (rank 7+) | SQL errors, connections, system issues |
_debugError() |
Superadmin (rank 8+) | Sensitive debug info |
// User sees this $this->_userError('login', 'Invalid email or password'); // Only developers see this $this->_systemError('login', "SQL: {$sql}\nError: {$db_error}");
Public Accessors
// Get all errors $errors = $instance->getErrors(); // Check for errors if ($instance->hasErrors('save')) { ... } if ($instance->hasErrors()) { ... } // Any context // Count errors $count = $instance->errorCount('save'); $total = $instance->errorCount(); // Clear errors $instance->clearErrors('save'); // Specific context $instance->clearErrors(); // All // Get first error message $message = $instance->firstError('save'); $message = $instance->firstError('save', AccessRank::GUEST->value); // Get all messages $messages = $instance->errorMessages('save'); $messages = $instance->errorMessages('save', AccessRank::GUEST->value);
Integration with InstanceError
use TimeFrontiers\InstanceError; $service = new MyService(); if (!$service->process()) { // Get errors filtered by current user's rank $errors = (new InstanceError($service))->get('process'); // Get all errors (bypass rank filter) $all = (new InstanceError($service, true))->get('process'); // Get only messages $messages = (new InstanceError($service))->get('process', true); }
Complete Example
use TimeFrontiers\Helper\HasErrors; use TimeFrontiers\AccessRank; class OrderService { use HasErrors; public function __construct( private OrderRepository $orders, private PaymentGateway $payment ) {} public function placeOrder(array $data):Order|false { // Validate if (empty($data['items'])) { $this->_userError('placeOrder', 'Cart is empty'); return false; } // Create order $order = new Order($data); if (!$this->orders->save($order)) { $this->_mergeErrors($this->orders, 'save', 'placeOrder'); $this->_userError('placeOrder', 'Unable to create order. Please try again.'); return false; } // Process payment if (!$this->payment->charge($order->total)) { $this->_systemError('placeOrder', 'Payment failed: ' . $this->payment->lastError()); $this->_userError('placeOrder', 'Payment declined. Please check your card details.'); // Rollback $this->orders->delete($order); return false; } return $order; } } // Controller usage $service = new OrderService($orders, $payment); if ($order = $service->placeOrder($request->all())) { return Response::success('Order placed', $order); } // Get user-appropriate errors $errors = (new InstanceError($service))->get('placeOrder', true); return Response::error('Order failed', $errors);
Migration from Legacy Pattern
Before:
class MyClass { public $errors = []; public function doSomething():bool { if ($failed) { $this->errors['doSomething'][] = [0, 256, 'Failed', __FILE__, __LINE__]; return false; } return true; } }
After:
class MyClass { use HasErrors; public function doSomething():bool { if ($failed) { $this->_userError('doSomething', 'Failed'); return false; } return true; } }
License
MIT License