validateInput($month, $year); $this->initializeDate($month, $year); $this->initFrom = $initFrom; $this->forceLiveCalculation = $forceLiveCalculation; // Dependency Injection mit Fallback $this->repository = $repository ?? new BusinessUserRepository($month, $year); $this->renderer = $renderer ?? new TreeHtmlRenderer($initFrom, $forceLiveCalculation); $this->logger = $logger ?? app(LoggerInterface::class); } /** * Initialisiert die Business-Struktur für Admin-Ansicht * * @param bool $check Prüft auf gespeicherte Struktur * @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten */ public function initStructureAdmin(bool $check = true, bool $forceLiveCalculation = false): void { try { $this->forceLiveCalculation = $forceLiveCalculation; if ($forceLiveCalculation) { $this->logger->info("Building fresh business structure for {$this->date->month}/{$this->date->year} with forced live calculation"); $this->buildFreshStructure(); return; } $storedStructure = null; if ($check) { $storedStructure = $this->repository->getStoredStructure(); } if ($storedStructure) { $this->logger->info("Loading stored business structure for {$this->date->month}/{$this->date->year}"); $this->loadStoredStructure($storedStructure); } else { $this->logger->info("Building fresh business structure for {$this->date->month}/{$this->date->year}"); $this->buildFreshStructure(); } } catch (\Exception $e) { $this->logger->error("Error initializing admin structure: " . $e->getMessage()); throw $e; } } /** * Initialisiert die Struktur für einen spezifischen User * * @param int $userId Die User-ID * @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten */ public function initStructureUser(int $userId, bool $forceLiveCalculation = false): void { try { $this->forceLiveCalculation = $forceLiveCalculation; if ($forceLiveCalculation) { $this->logger->info("Initializing structure for user: {$userId} with forced live calculation"); } else { $this->logger->info("Initializing structure for user: {$userId}"); } $user = $this->repository->getUserWithRelations($userId); if (!$user) { $this->logger->warning("User not found: {$userId}"); return; } $businessUserItem = new BusinessUserItemOptimized($this->date, $this); $businessUserItem->makeUserFromModel($user); // Erst User-Model laden, ohne forceLiveCalculation $this->addUserIdToProcessed($userId); $this->businessUsers[] = $businessUserItem; $this->logger->info("Created businessUserItem for user {$userId}, total businessUsers: " . count($this->businessUsers)); // Prüfe gespeicherte Struktur nur, wenn Live-Berechnung nicht erzwungen wird $storedStructure = null; if (!$forceLiveCalculation) { $storedStructure = $this->repository->getStoredStructure(); $this->logger->info("Stored structure " . ($storedStructure ? "found" : "not found")); } if ($storedStructure && !$forceLiveCalculation) { $this->loadStoredParentsUsers($storedStructure); if (isset($this->businessUsers[0]) && $this->businessUsers[0]->sponsor) { $this->loadStoredSponsorUser($this->businessUsers[0]->sponsor->user_id); } } else { if ($forceLiveCalculation) { $this->logger->info("Forcing live calculation - skipping stored structure for user {$userId}"); } $this->loadParentsUsers(); $this->loadSponsorUser($userId); $totalSubUsers = 0; foreach ($this->businessUsers as $businessUser) { $totalSubUsers += count($businessUser->businessUserItems); } $this->logger->info("After loadParentsUsers: {$totalSubUsers} total sub-users loaded across " . count($this->businessUsers) . " business users"); // WICHTIG: calcQualPP() erst NACH loadParentsUsers() aufrufen, da Points benötigt werden if ($forceLiveCalculation) { $this->logger->info("Calculating qualification levels for all business users"); foreach ($this->businessUsers as $businessUser) { $businessUser->calcQualPP(); } //wird nicht benötigt, da hier nur die Points berechnet werden //$this->calculateQualPPForAllUsers(); // Auch für alle Sub-User } } } catch (\Exception $e) { $this->logger->error("Error initializing user structure for {$userId}: " . $e->getMessage()); throw $e; } } /** * Initialisiert detaillierte Business-User-Informationen * * @param User $user Das User-Model * @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten */ public function initBusinesslUserDetail(User $user, bool $forceLiveCalculation = false): void { try { $this->logger->info("Initializing business user details for: {$user->id}"); $this->businessUser = new BusinessUserItemOptimized($this->date, $this); $this->businessUser->makeUserFromModel($user, $forceLiveCalculation); // ✅ Nutzt bereits User-Objekt $this->businessUser->checkSponsor($user); // Führe vollständige Berechnung durch, wenn: // 1. Daten nicht gespeichert sind ODER // 2. Live-Berechnung erzwungen wird if (!$this->businessUser->isSave() || $forceLiveCalculation) { if ($forceLiveCalculation) { $this->logger->info("Forcing live calculation for user {$user->id}"); } // Aufbau der Struktur für den User in die unendliche Tiefe $this->businessUser->readParentsBusinessUsers($forceLiveCalculation); // Calculate Points in Lines (optimiert für Memory-Effizienz) if (count($this->businessUser->businessUserItems) > 0) { $this->calculateUserPointsOptimized($this->businessUser->businessUserItems, 1, $this->businessUser); } // Qualifikation nach qual_kp (KundenPoints) und qual_pp (PaylinePoints) $this->businessUser->calcQualPP(); } } catch (\Exception $e) { $this->logger->error("Error initializing business user details for {$user->id}: " . $e->getMessage()); throw $e; } } /** * Gibt Growth Bonus zurück (ab Linie 6) * Erweitert um Array/Object-Kompatibilität für business_lines */ public function getGrowthBonus(): array { if (!$this->businessUser || !$this->businessUser->business_lines) { return []; } if (count($this->businessUser->business_lines) > 6) { // Handle both array and object types (JSON deserialization inconsistency) if (is_array($this->businessUser->business_lines)) { $bLines = $this->businessUser->business_lines; } else { $bLines = $this->businessUser->business_lines->toArray(); } return array_slice($bLines, 6); } return []; } /** * Gibt Wert für spezifische Linie zurück */ public function getKeybyLine(int $line, string $key) { if (!$this->businessUser || !$this->businessUser->business_lines) { return 0; } $bLines = $this->businessUser->business_lines; if (!isset($bLines[$line])) { return 0; } $lineData = $bLines[$line]; if ($lineData instanceof stdClass) { return $lineData->{$key} ?? 0; } if (is_array($lineData)) { return $lineData[$key] ?? 0; } return 0; } /** * HTML-Rendering Methoden (Delegation an Renderer) */ public function makeHtmlTree(): string { return $this->renderer->renderTree($this->businessUsers); } public function makeParentlessHtml(): string { return $this->renderer->renderParentless($this->parentless); } public function makeSponsorHtml(): string { return $this->renderer->renderSponsor($this->sponsor); } /** * Getter-Methoden (Rückwärtskompatibilität) */ public function getItems(): array { return $this->businessUsers; } /** * Zählt die Gesamtanzahl aller User in der Struktur (rekursiv) */ public function getTotalUserCount(): int { $totalCount = 0; // Zähle alle Root-User $totalCount += count($this->businessUsers); // Zähle alle Unter-User rekursiv foreach ($this->businessUsers as $businessUser) { $totalCount += $this->countBusinessUserItems($businessUser); } // Zähle parentless User $totalCount += count($this->parentless); return $totalCount; } /** * Zählt BusinessUserItems rekursiv */ private function countBusinessUserItems($businessUserItem): int { $count = 0; if (isset($businessUserItem->businessUserItems) && is_array($businessUserItem->businessUserItems)) { $count += count($businessUserItem->businessUserItems); // Rekursiv durch alle Unter-Items zählen foreach ($businessUserItem->businessUserItems as $subItem) { $count += $this->countBusinessUserItems($subItem); } } return $count; } public function isParentless(): bool { return !empty($this->parentless); } /** * Static Methoden (Rückwärtskompatibilität) */ public static function isFromStored(int $month, int $year): ?UserBusinessStructure { $structure = UserBusinessStructure::where('year', $year) ->where('month', $month) ->first(); return ($structure && $structure->completed) ? $structure : null; } /** * Öffentliche Methode zum Hinzufügen einer User ID zu den verarbeiteten IDs */ public function addProcessedUserId(int $id): void { $this->addUserIdToProcessed($id); } public static function addUserID(int $id): void { // Deprecated: Statische Methode kann nicht auf Instanz-Variable zugreifen // Verwende stattdessen die Instanz-Methode addProcessedUserId() throw new \BadMethodCallException('addUserID ist deprecated. Verwende Instanz-Methode addProcessedUserId() stattdessen.'); } // ===== Private Methoden ===== /** * Validiert Eingabeparameter */ private function validateInput(int $month, int $year): void { if ($month < 1 || $month > 12) { throw new \InvalidArgumentException("Invalid month: {$month}"); } $currentYear = (int) date('Y'); if ($year < 2020 || $year > $currentYear + 1) { throw new \InvalidArgumentException("Invalid year: {$year}"); } } /** * Initialisiert Datums-Objekt */ private function initializeDate(int $month, int $year): void { $this->date = new stdClass(); $date = Carbon::parse($year . '-' . $month . '-1'); $this->date->month = $month; $this->date->year = $year; $this->date->start_date = $date->format('Y-m-d H:i:s'); $this->date->end_date = $date->endOfMonth()->format('Y-m-d H:i:s'); } /** * Lädt gespeicherte Struktur */ private function loadStoredStructure(UserBusinessStructure $structure): void { $this->loadStoredRootUsers($structure); $this->loadStoredParentsUsers($structure); $this->loadStoredParentlessUsers($structure); // Prüfe ob gespeicherte Daten vollständig sind, ansonsten berechne neu $this->validateAndRecalculateIfNeeded(); $this->validateAndRecalculateParentlessIfNeeded(); } /** * Baut frische Struktur auf */ private function buildFreshStructure(): void { $this->loadRootUsers(); $this->loadParentsUsers(); $this->loadParentlessUsers(); // WICHTIG: Berechne Punkte und Qualifikationen für alle Business-Users $this->calculateAllBusinessUsers(); $this->calculateAllParentlessUsers(); } /** * Lädt Root-Users (optimiert mit Memory-Monitoring) */ private function loadRootUsers(): void { $startMemory = memory_get_usage(); $users = $this->repository->getRootUsers(); foreach ($users as $user) { // Memory-Check vor jeder User-Verarbeitung $this->checkMemoryUsage('loadRootUsers', $user->id); $businessUserItem = new BusinessUserItemOptimized($this->date, $this); $businessUserItem->makeUserFromModel($user, $this->forceLiveCalculation); // ✅ Nutzt bereits geladene Relations mit forceLiveCalculation $this->addUserIdToProcessed($user->id); $this->businessUsers[] = $businessUserItem; } $endMemory = memory_get_usage(); $memoryUsed = $this->formatBytes($endMemory - $startMemory); $this->logger->info("Loaded " . count($users) . " root users with optimized relations. Memory used: {$memoryUsed}"); } /** * Lädt Parent-Users für alle Business-Users */ private function loadParentsUsers(): void { $this->logger->info("Loading parent users for " . count($this->businessUsers) . " business users"); foreach ($this->businessUsers as $businessUser) { $businessUser->readParentsBusinessUsers($this->forceLiveCalculation); $this->logger->debug("Loaded " . count($businessUser->businessUserItems) . " parent users for user " . ($businessUser->b_user->user_id ?? 'unknown')); } } /** * Lädt parentlose Users (Memory-optimiert) */ private function loadParentlessUsers(): void { $count = 0; $excludeIds = array_keys($this->processedUserIds); foreach ($this->repository->getParentlessUsers($excludeIds) as $user) { $businessUserItem = new BusinessUserItemOptimized($this->date, $this); $businessUserItem->makeUserFromModel($user, $this->forceLiveCalculation); // ✅ Nutzt bereits geladene Relations mit forceLiveCalculation $this->parentless[] = $businessUserItem; $count++; } $this->logger->info("Loaded {$count} parentless users with optimized relations"); } /** * Berechnet Punkte und Qualifikationen für alle Business-Users */ private function calculateAllBusinessUsers(): void { $startTime = microtime(true); $this->logger->info("Starting calculation for " . count($this->businessUsers) . " business users"); foreach ($this->businessUsers as $businessUser) { try { // Berechne Punkte in Linien (wie bei initBusinesslUserDetail) if (count($businessUser->businessUserItems) > 0) { $this->calculateUserPointsOptimized($businessUser->businessUserItems, 1, $businessUser); } // Qualifikation nach qual_kp und qual_pp berechnen $businessUser->calcQualPP(); } catch (\Exception $e) { $this->logger->error("Error calculating business user {$businessUser->__get('user_id')}: " . $e->getMessage()); // Weiter mit dem nächsten User, nicht abbrechen continue; } } $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); $this->logger->info("Completed calculations for all business users in {$executionTime}ms"); } /** * Berechnet Punkte und Qualifikationen für alle Parentless-Users */ private function calculateAllParentlessUsers(): void { if (empty($this->parentless)) { return; } $startTime = microtime(true); $this->logger->info("Starting calculation for " . count($this->parentless) . " parentless users"); foreach ($this->parentless as $parentlessUser) { try { // Berechne Punkte in Linien if (count($parentlessUser->businessUserItems) > 0) { $this->calculateUserPointsOptimized($parentlessUser->businessUserItems, 1, $parentlessUser); } // Qualifikation berechnen $parentlessUser->calcQualPP(); } catch (\Exception $e) { $this->logger->error("Error calculating parentless user {$parentlessUser->__get('user_id')}: " . $e->getMessage()); continue; } } $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); $this->logger->info("Completed calculations for all parentless users in {$executionTime}ms"); } /** * Validiert gespeicherte Daten und berechnet bei Bedarf neu */ private function validateAndRecalculateIfNeeded(): void { $incompleteUsers = 0; foreach ($this->businessUsers as $businessUser) { // Prüfe ob grundlegende Berechnungen vorhanden sind if ($this->isBusinessUserIncomplete($businessUser)) { $incompleteUsers++; try { // Führe fehlende Berechnungen durch if (count($businessUser->businessUserItems) > 0) { $this->calculateUserPointsOptimized($businessUser->businessUserItems, 1, $businessUser); } $businessUser->calcQualPP(); } catch (\Exception $e) { $this->logger->error("Error recalculating business user {$businessUser->__get('user_id')}: " . $e->getMessage()); } } } if ($incompleteUsers > 0) { $this->logger->info("Recalculated {$incompleteUsers} incomplete business users from stored data"); } } /** * Prüft ob ein BusinessUser unvollständige Daten hat * Erweitert um Level-Qualifikationsdaten für Struktur-Ansicht */ private function isBusinessUserIncomplete($businessUser): bool { // Prüfe grundlegende Felder die nach Berechnungen vorhanden sein sollten $salesVolumeSum = $businessUser->__get('sales_volume_points_sum'); $qualKp = $businessUser->__get('qual_kp'); // Prüfe Level-Qualifikationsdaten für Struktur-Ansicht $nextQualUserLevel = $businessUser->__get('next_qual_user_level'); $nextCanUserLevel = $businessUser->__get('next_can_user_level'); $hasLevelQualificationData = !empty($nextQualUserLevel) || !empty($nextCanUserLevel); // User ist unvollständig wenn: // 1. Grundlegende berechnete Werte fehlen ODER // 2. Level-Qualifikationsdaten fehlen (wichtig für Struktur-Ansicht mit grünen Pfeilen) $missingBasicData = ($salesVolumeSum === null || $salesVolumeSum === 0) && ($qualKp === null || $qualKp === 0); $missingLevelData = !$hasLevelQualificationData; return $missingBasicData || $missingLevelData; } /** * Validiert und berechnet parentless Users bei Bedarf neu */ private function validateAndRecalculateParentlessIfNeeded(): void { if (empty($this->parentless)) { return; } $incompleteUsers = 0; foreach ($this->parentless as $parentlessUser) { if ($this->isBusinessUserIncomplete($parentlessUser)) { $incompleteUsers++; try { if (count($parentlessUser->businessUserItems) > 0) { $this->calculateUserPointsOptimized($parentlessUser->businessUserItems, 1, $parentlessUser); } $parentlessUser->calcQualPP(); } catch (\Exception $e) { $this->logger->error("Error recalculating parentless user {$parentlessUser->__get('user_id')}: " . $e->getMessage()); } } } if ($incompleteUsers > 0) { $this->logger->info("Recalculated {$incompleteUsers} incomplete parentless users from stored data"); } } /** * Lädt Sponsor für User */ private function loadSponsorUser(int $userId): void { try { $sponsorUser = $this->repository->getSponsorForUser($userId); if ($sponsorUser) { $this->sponsor = new BusinessUserItemOptimized($this->date, $this); $this->sponsor->makeUser($sponsorUser->id); $this->logger->info("Loaded sponsor {$sponsorUser->id} for user {$userId}"); } } catch (\Exception $e) { $this->logger->warning("Could not load sponsor for user {$userId}: " . $e->getMessage()); } } /** * Gespeicherte Root-Users laden */ private function loadStoredRootUsers(UserBusinessStructure $structure): void { if (!$structure->structure) { return; } foreach ($structure->structure as $obj) { $businessUserItem = new BusinessUserItemOptimized($this->date, $this); $businessUserItem->makeUser($obj->user_id); $this->addUserIdToProcessed($obj->user_id); $this->businessUsers[] = $businessUserItem; } } /** * Gespeicherte Parent-Users laden */ private function loadStoredParentsUsers(UserBusinessStructure $structure): void { foreach ($this->businessUsers as $businessUser) { $businessUser->readStoredParentsBusinessUsers($structure->structure); } } /** * Gespeicherte parentlose Users laden */ private function loadStoredParentlessUsers(UserBusinessStructure $structure): void { if (!$structure->parentless) { return; } foreach ($structure->parentless as $obj) { if (!isset($this->processedUserIds[$obj->user_id])) { $businessUserItem = new BusinessUserItemOptimized($this->date, $this); $businessUserItem->makeUser($obj->user_id); $this->parentless[] = $businessUserItem; } } } /** * Gespeicherten Sponsor laden */ private function loadStoredSponsorUser(int $userId): void { $this->sponsor = new BusinessUserItemOptimized($this->date, $this); $this->sponsor->makeUser($userId); } /** * Optimierte Punkte-Berechnung (Stack-basiert mit korrekter Depth-First Reihenfolge) * * KRITISCH: Stack muss gleiche Reihenfolge wie Original-Rekursion produzieren * Original: Depth-First Traversierung (erst tief, dann Punkte addieren) * Stack: Muss umgekehrt arbeiten - erst alle Kinder sammeln, dann von tief zu flach verarbeiten */ private function calculateUserPointsOptimized(array $businessUserItems, int $startLine, BusinessUserItemOptimized $businessUserToUpdate): void { $processingStack = []; $collectionStack = []; // Sammelt Items in korrekter Reihenfolge // Phase 1: Sammle alle Items in Depth-First Reihenfolge foreach ($businessUserItems as $item) { $collectionStack[] = ['item' => $item, 'line' => $startLine, 'depth' => 0]; } // Expandiere alle Kinder (Depth-First) $processedItems = []; while (!empty($collectionStack)) { $current = array_shift($collectionStack); // FIFO für Breadth-First Sammlung $item = $current['item']; $line = $current['line']; $depth = $current['depth']; // Markiere für Verarbeitung (mit Tiefe für spätere Sortierung) $processingStack[] = [ 'item' => $item, 'line' => $line, 'depth' => $depth, 'id' => $item->user_id ?? uniqid() ]; // Füge Kinder hinzu (werden später verarbeitet = Depth-First) if (isset($item->businessUserItems) && count($item->businessUserItems) > 0) { // Kinder in umgekehrter Reihenfolge hinzufügen für korrekte Stack-Verarbeitung $children = array_reverse($item->businessUserItems); foreach ($children as $childItem) { array_unshift($collectionStack, [ 'item' => $childItem, 'line' => $line + 1, 'depth' => $depth + 1 ]); } } } // Phase 2: Sortiere nach Tiefe (tiefste zuerst, wie bei Rekursion) usort($processingStack, function ($a, $b) { return $b['depth'] <=> $a['depth']; // Tiefste zuerst }); // Phase 3: Verarbeite in korrekter Reihenfolge (von tief zu flach) foreach ($processingStack as $current) { $item = $current['item']; $line = $current['line']; try { // Business Line initialisieren falls nötig if (!isset($businessUserToUpdate->business_lines[$line])) { $obj = new stdClass(); $obj->points = 0; $businessUserToUpdate->addBusinessLineToUser($line, $obj); } // Punkte hinzufügen (mit Validierung) $points = (float) ($item->sales_volume_points_TP_sum ?? 0); if ($points > 0) { $businessUserToUpdate->addBusinessLinePoints($line, $points); $businessUserToUpdate->addTotalTP($points); } $this->logger->debug("Processed user {$current['id']} at line {$line} with {$points} points"); } catch (\Exception $e) { $this->logger->error("Error processing user points for {$current['id']}: " . $e->getMessage()); } } $this->logger->info("Processed " . count($processingStack) . " business user items in depth-first order"); } /** * User-ID zu verarbeiteten IDs hinzufügen */ private function addUserIdToProcessed(int $id): void { $this->processedUserIds[$id] = true; } /** * Prüft ob User bereits verarbeitet wurde (Public für BusinessUserItemOptimized) */ public function isUserProcessed(int $id): bool { return isset($this->processedUserIds[$id]); } /** * Memory-Monitoring Methoden */ private function checkMemoryUsage(string $operation, $identifier = null): void { $currentMemory = memory_get_usage(); $memoryLimit = $this->parseMemoryLimit(ini_get('memory_limit')); $memoryPercent = ($currentMemory / $memoryLimit) * 100; if ($memoryPercent > 80) { $currentFormatted = $this->formatBytes($currentMemory); $limitFormatted = $this->formatBytes($memoryLimit); $this->logger->warning("High memory usage detected in {$operation}", [ 'identifier' => $identifier, 'current_memory' => $currentFormatted, 'memory_limit' => $limitFormatted, 'usage_percent' => round($memoryPercent, 2) ]); // Garbage Collection bei hohem Memory-Verbrauch if ($memoryPercent > 90) { $this->logger->warning("Critical memory usage - forcing garbage collection"); gc_collect_cycles(); } } } private function parseMemoryLimit(string $limit): int { $limit = trim($limit); $last = strtolower($limit[strlen($limit) - 1]); $number = (int) $limit; switch ($last) { case 'g': $number *= 1024; case 'm': $number *= 1024; case 'k': $number *= 1024; } return $number; } private function formatBytes(int $bytes, int $precision = 2): string { $units = array('B', 'KB', 'MB', 'GB', 'TB'); for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, $precision) . ' ' . $units[$i]; } /** * Public Properties für Rückwärtskompatibilität */ public function __get(string $name) { switch ($name) { case 'date': return $this->date; case 'business_user': return $this->businessUser; case 'business_users': return $this->businessUsers; case 'parentless': return $this->parentless; default: throw new \InvalidArgumentException("Property {$name} does not exist"); } } /** * Berechnet calcQualPP() für alle BusinessUsers rekursiv * Muss NACH loadParentsUsers() aufgerufen werden, da Points benötigt werden */ private function calculateQualPPForAllUsers(): void { $this->logger->info("Starting recursive calcQualPP for all users"); $totalCalculated = 0; foreach ($this->businessUsers as $businessUser) { $totalCalculated += $this->calculateQualPPRecursive($businessUser); } $this->logger->info("Completed calcQualPP for {$totalCalculated} users"); } /** * Rekursive Hilfsmethode für calcQualPP */ private function calculateQualPPRecursive($businessUser): int { $calculated = 0; if (isset($businessUser->businessUserItems) && is_array($businessUser->businessUserItems)) { foreach ($businessUser->businessUserItems as $subBusinessUser) { if ($subBusinessUser->b_user && $subBusinessUser->b_user->user_id) { try { $subBusinessUser->calcQualPP(); $calculated++; $this->logger->debug("Calculated calcQualPP for user " . $subBusinessUser->b_user->user_id); } catch (\Exception $e) { $this->logger->warning("Error calculating calcQualPP for user " . $subBusinessUser->b_user->user_id . ": " . $e->getMessage()); } // Rekursiver Aufruf $calculated += $this->calculateQualPPRecursive($subBusinessUser); } } } return $calculated; } public function __set(string $name, $value) { switch ($name) { case 'business_users': $this->businessUsers = $value; break; case 'parentless': $this->parentless = $value; break; default: throw new \InvalidArgumentException("Property {$name} cannot be set"); } } }