-
Notifications
You must be signed in to change notification settings - Fork 0
Feature Wiki
This comprehensive guide covers all features available in PersonalAccounter and provides detailed implementation examples for extending the application.
- Dashboard & Analytics
- Expense Management
- Payment Methods
- Subscription Management
- Categories & Tags
- User Management & Authentication
- Reporting & Export
- API System
- Security Features
- Extending the Application
The dashboard provides a comprehensive financial overview with real-time statistics, visual analytics, and configurable date filtering.
- Total Expenses: Sum of all expenses with optional date filtering
- Subscription Costs: Monthly and annual projections for recurring services
- Payment Method Breakdown: Distribution across credit cards, bank accounts, and crypto wallets
- Category Analysis: Spending patterns by expense categories
- Status Tracking: Pending, approved, rejected, and paid expense counts
- Spending Trends: Monthly expense trends with interactive charts
- Category Pie Charts: Visual representation of expense distribution
- Subscription Growth: Timeline of subscription additions and cancellations
- Payment Method Usage: Frequency and amount analysis by payment type
- Flexible Periods: Custom date ranges, preset periods (week, month, quarter, year)
- Real-time Updates: AJAX-powered filtering without page reloads
- Comparative Analysis: Year-over-year and month-over-month comparisons
Create a new widget by extending the dashboard controller:
// In app/Controllers/DashboardController.php
private function getCustomMetric($userId, $fromDate = null, $toDate = null) {
$conditions = ['user_id' => $userId];
if ($fromDate && $toDate) {
$conditions['created_at[>=]'] = $fromDate . ' 00:00:00';
$conditions['created_at[<=]'] = $toDate . ' 23:59:59';
}
// Your custom metric calculation
$result = $this->db->select('your_table', [
'COUNT(*) as count',
'SUM(amount) as total'
], $conditions);
return [
'count' => $result[0]['count'] ?? 0,
'total' => floatval($result[0]['total'] ?? 0)
];
}
Add new chart types by creating JavaScript chart configurations:
// In public/js/main.js
function createCustomChart(data) {
const ctx = document.getElementById('custom-chart').getContext('2d');
new Chart(ctx, {
type: 'line', // or 'bar', 'pie', 'doughnut'
data: {
labels: data.labels,
datasets: [{
label: 'Custom Metric',
data: data.values,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
The expense management system provides comprehensive tracking, categorization, and approval workflows for all financial expenditures.
- Multi-field Support: Title, description, amount, currency, dates
- Tax Calculations: Automatic tax amount calculation with customizable rates
- File Attachments: Receipt and invoice uploads with file management
- Payment Method Association: Link to credit cards, bank accounts, or crypto wallets
- Category & Tag Assignment: Flexible organization system
- Status Management: Pending → Approved → Paid workflow
- Rejection Handling: Rejected expenses with reason tracking
- Transaction Generation: Automatic transaction creation on approval
- Audit Trail: Complete history of status changes
- Import from Excel/CSV: Column mapping with validation
- Export Capabilities: Multiple format support (Excel, CSV, JSON)
- Batch Actions: Mass approval, rejection, or status updates
- Template System: Standardized import templates
// Tax calculation example
$taxRate = 8.5; // 8.5%
$amount = 100.00;
$taxAmount = ($amount * $taxRate) / 100; // $8.50
$totalAmount = $amount + $taxAmount; // $108.50
Create recurring expense templates:
// In app/Models/Expense.php
public function createRecurringExpense($templateId, $date) {
$template = $this->getRecurringTemplate($templateId);
$expenseData = [
'title' => $template['title'],
'amount' => $template['amount'],
'category_id' => $template['category_id'],
'expense_date' => $date,
'status' => 'pending'
];
return $this->create($expenseData);
}
Add new expense types by extending the enum:
// In database migrations
$table->enum('expense_type', [
'business',
'personal',
'travel',
'entertainment',
'medical',
'custom_type' // Your new type
])->default('personal');
Implement multi-level approval:
public function requiresMultiLevelApproval($expense) {
// High-value expenses need manager approval
if ($expense['amount'] > 1000) {
return true;
}
// Certain categories require special approval
$restrictedCategories = ['travel', 'equipment'];
if (in_array($expense['category'], $restrictedCategories)) {
return true;
}
return false;
}
PersonalAccounter supports multiple payment method types with international banking standards and cryptocurrency integration.
- Basic Information: Card name, bank name, last 4 digits
- Currency Support: Multi-currency card tracking
- Expiration Management: Expiry date tracking and notifications
- Usage Analytics: Spending patterns and limits
- International Support: IBAN, SWIFT/BIC codes
- Account Types: Checking, savings, business, money market, CD
- Multi-currency: Support for 13+ major currencies
- Validation: IBAN format validation and checksum verification
- Multi-network Support: Bitcoin, Ethereum, Polygon, BSC, etc.
- Address Validation: Cryptocurrency address format validation
- Network Detection: Automatic network identification
- Balance Tracking: Integration capabilities for balance monitoring
// IBAN validation example
public function validateIBAN($iban) {
// Remove spaces and convert to uppercase
$iban = strtoupper(str_replace(' ', '', $iban));
// Check length (15-34 characters)
if (strlen($iban) < 15 || strlen($iban) > 34) {
return false;
}
// Check country code and calculate checksum
$countryCode = substr($iban, 0, 2);
$checkDigits = substr($iban, 2, 2);
$accountIdentifier = substr($iban, 4);
// Rearrange for checksum calculation
$rearranged = $accountIdentifier . $countryCode . '00';
// Convert letters to numbers (A=10, B=11, etc.)
$numericString = '';
for ($i = 0; $i < strlen($rearranged); $i++) {
$char = $rearranged[$i];
if (ctype_alpha($char)) {
$numericString .= (ord($char) - ord('A') + 10);
} else {
$numericString .= $char;
}
}
// Calculate mod 97
$remainder = bcmod($numericString, '97');
$calculatedCheck = 98 - $remainder;
return sprintf('%02d', $calculatedCheck) === $checkDigits;
}
Create a new payment method type:
// Create migration for new payment type
class CreateDigitalWalletsTable extends Migration {
public function up() {
$this->createTable('digital_wallets', function($table) {
$table->id();
$table->integer('user_id')->index();
$table->string('name'); // PayPal, Venmo, etc.
$table->string('service_type'); // paypal, venmo, cashapp
$table->string('account_identifier'); // email or username
$table->string('currency', 3)->default('USD');
$table->text('notes')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
}
Connect to banking APIs for real-time balance:
class BankAccountService {
public function getAccountBalance($bankAccount) {
// Integration with Open Banking API
$client = new BankingAPIClient([
'api_key' => Config::get('banking.api_key'),
'base_url' => Config::get('banking.base_url')
]);
try {
$response = $client->getBalance([
'account_id' => $bankAccount['external_id'],
'iban' => $bankAccount['iban']
]);
return [
'balance' => $response['available_balance'],
'currency' => $response['currency'],
'last_updated' => now()
];
} catch (Exception $e) {
AppLogger::error('Failed to fetch bank balance', [
'account_id' => $bankAccount['id'],
'error' => $e->getMessage()
]);
return null;
}
}
}
Track and manage all recurring subscriptions with comprehensive billing cycle support and cost projections.
- Service Details: Name, plan, provider information
- Billing Cycles: Monthly, annual, weekly, daily, one-time
- Cost Management: Amount tracking with currency conversion
- Status Lifecycle: Active, paused, expired, cancelled
- Monthly Costs: Aggregated monthly spending projections
- Annual Forecasts: Yearly cost calculations
- Budget Planning: Cost trend analysis and warnings
- ROI Tracking: Value assessment for business subscriptions
- Automatic Detection: Next billing date calculations
- Notification System: Upcoming renewal alerts
- Cancellation Tracking: Cancelled service monitoring
- Reactivation: Easy subscription restart
public function calculateProjectedCosts($subscriptions) {
$monthlyTotal = 0;
$annualTotal = 0;
foreach ($subscriptions as $sub) {
$amount = floatval($sub['amount']);
switch ($sub['billing_cycle']) {
case 'monthly':
$monthlyTotal += $amount;
$annualTotal += $amount * 12;
break;
case 'annual':
$monthlyTotal += $amount / 12;
$annualTotal += $amount;
break;
case 'weekly':
$monthlyTotal += $amount * 4.33; // Average weeks per month
$annualTotal += $amount * 52;
break;
case 'daily':
$monthlyTotal += $amount * 30;
$annualTotal += $amount * 365;
break;
}
}
return [
'monthly_total' => round($monthlyTotal, 2),
'annual_total' => round($annualTotal, 2)
];
}
Add new billing frequencies:
// Extend billing cycle enum
$validCycles = [
'monthly',
'annual',
'weekly',
'daily',
'onetime',
'quarterly', // New cycle
'biannual' // New cycle
];
Connect to service APIs for automatic updates:
class SubscriptionSyncService {
public function syncWithProvider($subscription) {
switch ($subscription['provider']) {
case 'spotify':
return $this->syncSpotify($subscription);
case 'netflix':
return $this->syncNetflix($subscription);
default:
return $this->manualSync($subscription);
}
}
private function syncSpotify($subscription) {
// Spotify API integration
$client = new SpotifyAPIClient();
$account = $client->getSubscription($subscription['external_id']);
return [
'status' => $account['status'],
'next_billing_date' => $account['next_payment_due'],
'amount' => $account['subscription_amount']
];
}
}
Implement intelligent notification system:
public function getUpcomingRenewals($daysAhead = 7) {
$renewals = $this->db->select('subscriptions', '*', [
'status' => 'active',
'next_billing_date[<=]' => date('Y-m-d', strtotime("+{$daysAhead} days"))
]);
$notifications = [];
foreach ($renewals as $renewal) {
$daysUntil = ceil((strtotime($renewal['next_billing_date']) - time()) / 86400);
$notifications[] = [
'subscription' => $renewal,
'days_until_renewal' => $daysUntil,
'urgency' => $daysUntil <= 1 ? 'high' : ($daysUntil <= 3 ? 'medium' : 'low')
];
}
return $notifications;
}
Flexible organization system using hierarchical categories and multi-tag support for comprehensive expense classification.
- Parent Categories: Top-level groupings (Food, Transport, Utilities)
- Sub-categories: Detailed classifications (Restaurants, Gas, Electricity)
- Color Coding: Visual identification with customizable colors
- Icon Support: Font Awesome icons for visual recognition
Built-in category templates:
- Business: Office supplies, travel, meals, equipment
- Personal: Food, entertainment, shopping, health
- Home: Utilities, maintenance, furniture, insurance
- Transport: Fuel, public transport, parking, maintenance
- Flexible Tagging: Multiple tags per expense
- Tag Hierarchy: Optional parent-child relationships
- Auto-suggestions: Popular tag recommendations
- Quick Creation: Instant tag creation during expense entry
- Usage Analytics: Most frequently used tags
- Trending Tags: Recently popular tags
- User-specific: Personalized tag suggestions
- Global Trends: System-wide popular tags
Create specialized category systems:
// Business-specific categories
class BusinessCategorySeeder {
public function seed() {
$categories = [
[
'name' => 'R&D',
'description' => 'Research and Development',
'color' => '#3B82F6',
'icon' => 'fas fa-flask',
'parent_id' => null
],
[
'name' => 'Software Licenses',
'description' => 'Software and SaaS subscriptions',
'color' => '#10B981',
'icon' => 'fas fa-code',
'parent_id' => 1 // R&D parent
]
];
foreach ($categories as $category) {
$this->categoryModel->create($category);
}
}
}
Implement smart categorization:
class SmartCategorizationService {
public function suggestCategory($expenseTitle, $description = '', $vendor = '') {
$text = strtolower($expenseTitle . ' ' . $description . ' ' . $vendor);
$rules = [
'food' => ['restaurant', 'cafe', 'food', 'lunch', 'dinner', 'starbucks'],
'transport' => ['uber', 'taxi', 'gas', 'fuel', 'parking', 'metro'],
'utilities' => ['electric', 'water', 'internet', 'phone', 'cable'],
'entertainment' => ['movie', 'cinema', 'netflix', 'spotify', 'game']
];
foreach ($rules as $category => $keywords) {
foreach ($keywords as $keyword) {
if (strpos($text, $keyword) !== false) {
return $this->categoryModel->findByName($category);
}
}
}
return null; // No suggestion
}
}
Advanced tag usage analytics:
public function getTagInsights($userId, $period = '30 days') {
$fromDate = date('Y-m-d', strtotime("-{$period}"));
$tagUsage = $this->db->query("
SELECT
t.name,
t.color,
COUNT(et.expense_id) as usage_count,
SUM(e.amount) as total_amount,
AVG(e.amount) as avg_amount
FROM tags t
JOIN expense_tags et ON t.id = et.tag_id
JOIN expenses e ON et.expense_id = e.id
WHERE e.user_id = ? AND e.expense_date >= ?
GROUP BY t.id
ORDER BY usage_count DESC
", [$userId, $fromDate])->fetchAll();
return [
'period' => $period,
'tag_usage' => $tagUsage,
'most_expensive_tag' => $tagUsage[0] ?? null,
'total_tagged_expenses' => array_sum(array_column($tagUsage, 'usage_count'))
];
}
Comprehensive user management system with role-based access control and advanced security features.
- Google Authenticator: TOTP-based authentication
- Backup Codes: Recovery codes for account access
- QR Code Generation: Easy setup with mobile apps
- Forced 2FA: Admin-configurable mandatory 2FA
- Admin Role: Full system access except user management
- Superadmin Role: Complete system control including user management
- Permission System: Granular API permission control
- Role Inheritance: Hierarchical permission structure
- Secure Cookies: HttpOnly, Secure, SameSite attributes
- Session Regeneration: ID regeneration on authentication
- Timeout Management: Configurable session lifetimes
- Concurrent Session Control: Multiple session management
// Rate limiting implementation
private function isRateLimited() {
if (!isset($_SESSION['login_attempts'])) {
$_SESSION['login_attempts'] = [];
}
$timeout = Config::get('auth.login_attempts_timeout', 300);
$_SESSION['login_attempts'] = array_filter(
$_SESSION['login_attempts'],
function($timestamp) use ($timeout) {
return $timestamp > (time() - $timeout);
}
);
$limit = Config::get('auth.login_attempts_limit', 5);
return count($_SESSION['login_attempts']) >= $limit;
}
- Bcrypt Hashing: Strong password hashing with salt
- Timing Attack Prevention: Consistent verification timing
- Password Policies: Configurable complexity requirements
- History Tracking: Prevent password reuse
Add additional user profile fields:
// Migration for extended user profile
class AddUserProfileFields extends Migration {
public function up() {
$this->execute("
ALTER TABLE users
ADD COLUMN department VARCHAR(100),
ADD COLUMN employee_id VARCHAR(50),
ADD COLUMN manager_id INT NULL,
ADD COLUMN phone VARCHAR(20),
ADD COLUMN timezone VARCHAR(50) DEFAULT 'UTC'
");
}
}
Integrate with Active Directory:
class LDAPAuthService {
public function authenticate($username, $password) {
$ldap = ldap_connect(Config::get('ldap.server'));
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
$userDN = "uid={$username}," . Config::get('ldap.base_dn');
if (ldap_bind($ldap, $userDN, $password)) {
// Fetch user information
$search = ldap_search($ldap, Config::get('ldap.base_dn'), "(uid={$username})");
$userInfo = ldap_get_entries($ldap, $search);
// Create or update local user
return $this->syncLocalUser($userInfo[0]);
}
return false;
}
}
Implement SAML SSO:
class SAMLService {
public function handleSSOResponse($samlResponse) {
// Validate SAML response
$assertion = $this->validateSAMLResponse($samlResponse);
if ($assertion) {
$userAttributes = $this->extractUserAttributes($assertion);
return $this->createUserSession([
'email' => $userAttributes['email'],
'name' => $userAttributes['displayName'],
'role' => $this->mapSAMLRole($userAttributes['role'])
]);
}
return false;
}
}
Comprehensive reporting system with real-time analytics, flexible export options, and customizable report generation.
- Expense Reports: Detailed expense breakdowns with filtering
- Subscription Reports: Recurring cost analysis and projections
- Payment Method Reports: Usage and spending by payment type
- Category Reports: Spending patterns by category and tag
- Trend Analysis: Month-over-month and year-over-year comparisons
- Budget Variance: Actual vs. planned spending analysis
- ROI Reports: Return on investment for business expenses
- Tax Reports: Tax-deductible expense summaries
- User Activity: User engagement and system usage
- Audit Trails: Complete transaction and change histories
- System Health: Performance and error monitoring
- Data Quality: Missing or inconsistent data identification
- Excel (XLSX): Full formatting with charts and pivot tables
- CSV: Universal format for data analysis
- JSON: API-friendly structured data
- PDF: Formatted reports for sharing and archiving
class ReportBuilder {
public function createCustomReport($config) {
$report = new Report();
// Apply filters
foreach ($config['filters'] as $filter) {
$report->addFilter($filter['field'], $filter['operator'], $filter['value']);
}
// Select columns
$report->selectColumns($config['columns']);
// Apply grouping
if (isset($config['group_by'])) {
$report->groupBy($config['group_by']);
}
// Apply sorting
foreach ($config['sort'] as $sort) {
$report->orderBy($sort['field'], $sort['direction']);
}
return $report->generate();
}
}
Add new visualization types:
class CustomChartGenerator {
public function generateSankeyDiagram($expenseFlow) {
// Sankey diagram for expense flow visualization
$nodes = [];
$links = [];
foreach ($expenseFlow as $flow) {
$nodes[] = ['id' => $flow['source'], 'name' => $flow['source_name']];
$nodes[] = ['id' => $flow['target'], 'name' => $flow['target_name']];
$links[] = [
'source' => $flow['source'],
'target' => $flow['target'],
'value' => $flow['amount']
];
}
return [
'type' => 'sankey',
'data' => [
'nodes' => array_unique($nodes, SORT_REGULAR),
'links' => $links
]
];
}
}
Implement automated report generation:
class ScheduledReportService {
public function scheduleReport($config) {
$schedule = [
'report_id' => $config['report_id'],
'frequency' => $config['frequency'], // daily, weekly, monthly
'recipients' => $config['recipients'],
'format' => $config['format'],
'filters' => json_encode($config['filters'])
];
$this->db->insert('scheduled_reports', $schedule);
// Add to cron scheduler
$this->addToCronSchedule($schedule);
}
public function generateScheduledReport($scheduleId) {
$schedule = $this->getSchedule($scheduleId);
$report = $this->reportBuilder->generate($schedule['filters']);
// Export in specified format
$file = $this->exportReport($report, $schedule['format']);
// Send to recipients
$this->emailReport($file, $schedule['recipients']);
}
}
Comprehensive RESTful API with OpenAPI documentation, authentication, and rate limiting.
- API Keys: Header-based authentication with X-API-Key
- Bearer Tokens: OAuth-style token authentication
- Permission System: Granular endpoint access control
- Rate Limiting: Configurable request limits per API key
- Expenses: CRUD operations with filtering and search
- Subscriptions: Full subscription management
- Categories/Tags: Organization system management
- Reports: Analytics and export endpoints
- Users: User management (superadmin only)
- OpenAPI 3.0: Complete API specification
- Swagger UI: Interactive API documentation
- Code Examples: Sample requests and responses
- Authentication Guide: Implementation examples
Add new API endpoints:
// In app/Controllers/Api/CustomApiController.php
class CustomApiController extends ApiController {
/**
* @OA\Get(
* path="/api/v1/custom/analytics",
* summary="Get custom analytics",
* tags={"Custom"},
* security={{"ApiKeyAuth": {}}},
* @OA\Response(response=200, description="Analytics retrieved successfully")
* )
*/
public function getAnalytics() {
if (!$this->hasPermission('analytics.read')) {
$this->forbidden('Permission denied');
}
$analytics = $this->calculateCustomAnalytics();
$this->success($analytics, 'Analytics retrieved successfully');
}
private function calculateCustomAnalytics() {
// Your custom analytics logic
return [
'metric1' => 123,
'metric2' => 456,
'timestamp' => date('c')
];
}
}
Add webhook support for real-time integrations:
class WebhookService {
public function triggerWebhook($event, $data) {
$webhooks = $this->getActiveWebhooks($event);
foreach ($webhooks as $webhook) {
$this->sendWebhook($webhook, $event, $data);
}
}
private function sendWebhook($webhook, $event, $data) {
$payload = [
'event' => $event,
'data' => $data,
'timestamp' => time(),
'signature' => $this->generateSignature($webhook['secret'], $data)
];
$ch = curl_init($webhook['url']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Webhook-Signature: ' . $payload['signature']
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$this->logWebhookDelivery($webhook['id'], $httpCode, $response);
}
}
Implement sophisticated rate limiting:
class AdvancedRateLimiter {
public function checkRateLimit($apiKey, $endpoint) {
$limits = $this->getRateLimits($apiKey, $endpoint);
foreach ($limits as $window => $limit) {
$usage = $this->getUsage($apiKey, $endpoint, $window);
if ($usage >= $limit) {
return [
'allowed' => false,
'reset_time' => $this->getResetTime($window),
'limit' => $limit,
'remaining' => 0
];
}
}
return [
'allowed' => true,
'limit' => $limits['hour'],
'remaining' => $limits['hour'] - $this->getUsage($apiKey, $endpoint, 'hour')
];
}
}
Multi-layered security implementation with protection against common vulnerabilities and advanced threat detection.
- CSRF Protection: Token-based cross-site request forgery protection
- XSS Prevention: Input sanitization and output encoding
- SQL Injection: Prepared statements and parameterized queries
- File Upload Security: Type validation and sandboxing
- Password Hashing: Bcrypt with automatic salt generation
- Session Security: Secure cookie attributes and regeneration
- 2FA Implementation: Time-based one-time passwords
- Account Lockout: Automatic protection against brute force
- Rate Limiting: Request throttling per API key
- Input Validation: Strict parameter validation
- Authentication: Multiple authentication methods
- Audit Logging: Comprehensive request logging
class CSRFProtection {
public static function generateToken() {
if (!isset($_SESSION['_token'])) {
$_SESSION['_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['_token'];
}
public static function validateToken($token) {
$sessionToken = $_SESSION['_token'] ?? '';
return hash_equals($sessionToken, $token);
}
}
class InputSanitizer {
public static function sanitizeInput($input, $type = 'string') {
switch ($type) {
case 'email':
return filter_var($input, FILTER_SANITIZE_EMAIL);
case 'url':
return filter_var($input, FILTER_SANITIZE_URL);
case 'int':
return filter_var($input, FILTER_SANITIZE_NUMBER_INT);
case 'float':
return filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
default:
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
}
}
Implement suspicious activity detection:
class ThreatDetectionService {
public function analyzeRequest($request) {
$riskScore = 0;
$threats = [];
// Check for SQL injection patterns
if ($this->containsSQLInjection($request['body'])) {
$riskScore += 50;
$threats[] = 'sql_injection_attempt';
}
// Check for unusual request patterns
if ($this->isUnusualRequestPattern($request)) {
$riskScore += 30;
$threats[] = 'unusual_pattern';
}
// Check IP reputation
if ($this->isMaliciousIP($request['ip'])) {
$riskScore += 40;
$threats[] = 'malicious_ip';
}
return [
'risk_score' => $riskScore,
'threats' => $threats,
'action' => $this->determineAction($riskScore)
];
}
}
Implement field-level encryption:
class FieldEncryption {
private $key;
public function __construct() {
$this->key = Config::get('encryption.key');
}
public function encrypt($data) {
$iv = random_bytes(16);
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $this->key, 0, $iv);
return base64_encode($iv . $encrypted);
}
public function decrypt($encryptedData) {
$data = base64_decode($encryptedData);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
return openssl_decrypt($encrypted, 'AES-256-CBC', $this->key, 0, $iv);
}
}
- Models: Handle data logic and database operations
- Views: Template files for user interface
- Controllers: Business logic and request handling
- Services: Reusable business logic components
- Migrations: Version-controlled database changes
- Foreign Keys: Maintain referential integrity
- Indexing: Optimize query performance
- Normalization: Reduce data redundancy
- Create Migration
php control make migration create_custom_feature_table
- Create Model
class CustomFeature extends Model {
protected $table = 'custom_features';
public function getWithRelations() {
return $this->db->select($this->table, [
'[>]users' => ['user_id' => 'id']
], [
'custom_features.*',
'users.name(user_name)'
]);
}
}
- Create Controller
class CustomFeatureController extends Controller {
private $customFeatureModel;
public function __construct($db) {
$this->customFeatureModel = new CustomFeature($db);
}
public function index() {
$features = $this->customFeatureModel->getWithRelations();
$this->view('custom-features/index', ['features' => $features]);
}
}
- Add Routes
// In app/Routes/web.php
$router->get('/custom-features', function() use ($customFeatureController) {
$customFeatureController->index();
});
Create a plugin system:
class PluginManager {
private $plugins = [];
public function registerPlugin($name, $plugin) {
$this->plugins[$name] = $plugin;
}
public function executeHook($hookName, $data = null) {
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, $hookName)) {
$data = $plugin->$hookName($data);
}
}
return $data;
}
}
// Example plugin
class CustomAnalyticsPlugin {
public function beforeExpenseCreate($expenseData) {
// Add custom analytics tracking
$this->trackExpenseCreation($expenseData);
return $expenseData;
}
public function afterExpenseCreate($expense) {
// Send to external analytics service
$this->sendToAnalytics($expense);
return $expense;
}
}
class ExternalServiceConnector {
public function connectToQuickBooks($credentials) {
$client = new QuickBooksClient($credentials);
// Sync chart of accounts
$categories = $client->getChartOfAccounts();
$this->syncCategories($categories);
// Import transactions
$transactions = $client->getTransactions();
$this->importTransactions($transactions);
}
private function syncCategories($externalCategories) {
foreach ($externalCategories as $extCategory) {
$category = $this->categoryModel->findByExternalId($extCategory['id']);
if (!$category) {
$this->categoryModel->create([
'name' => $extCategory['name'],
'external_id' => $extCategory['id'],
'external_source' => 'quickbooks'
]);
}
}
}
}
This comprehensive feature documentation provides a thorough understanding of PersonalAccounter's capabilities and extensive examples for extending the application. Each section includes practical implementation examples and best practices for development.