Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 5.6 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Generator; 4 5use SocialDept\AtpSchema\Parser\Nsid; 6 7class NamingConverter 8{ 9 /** 10 * Base namespace for generated classes. 11 */ 12 protected string $baseNamespace; 13 14 /** 15 * PHP reserved keywords that cannot be used as class names. 16 */ 17 protected const RESERVED_KEYWORDS = [ 18 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 19 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 20 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 21 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 22 'finally', 'fn', 'for', 'foreach', 'function', 'global', 'goto', 'if', 23 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 24 'interface', 'isset', 'list', 'match', 'namespace', 'new', 'or', 'print', 25 'private', 'protected', 'public', 'readonly', 'require', 'require_once', 26 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 27 'var', 'while', 'xor', 'yield', '__halt_compiler', 28 ]; 29 30 /** 31 * Create a new NamingConverter. 32 */ 33 public function __construct(string $baseNamespace = 'App\\Lexicons') 34 { 35 $this->baseNamespace = rtrim($baseNamespace, '\\'); 36 } 37 38 /** 39 * Convert NSID to fully qualified class name. 40 */ 41 public function nsidToClassName(string $nsidString): string 42 { 43 $nsid = Nsid::parse($nsidString); 44 $namespace = $this->nsidToNamespace($nsidString); 45 $className = $this->toClassName($nsid->getName()); 46 47 return $namespace.'\\'.$className; 48 } 49 50 /** 51 * Convert NSID to namespace. 52 */ 53 public function nsidToNamespace(string $nsidString): string 54 { 55 $nsid = Nsid::parse($nsidString); 56 57 // Split authority into parts (e.g., "blog.pckt" -> ["blog", "pckt"]) 58 $authorityParts = explode('.', $nsid->getAuthority()); 59 60 // Convert each part to PascalCase 61 $namespaceParts = array_map( 62 fn ($part) => $this->toPascalCase($part), 63 $authorityParts 64 ); 65 66 return $this->baseNamespace.'\\'.implode('\\', $namespaceParts); 67 } 68 69 /** 70 * Get class name from NSID name part. 71 */ 72 public function toClassName(string $name): string 73 { 74 // Split on dots (e.g., "feed.post" -> "FeedPost") 75 $parts = explode('.', $name); 76 77 $className = implode('', array_map( 78 fn ($part) => $this->toPascalCase($part), 79 $parts 80 )); 81 82 // Check if the class name is a reserved keyword and add suffix 83 if ($this->isReservedKeyword($className)) { 84 $className .= 'Record'; 85 } 86 87 return $className; 88 } 89 90 /** 91 * Check if a name is a PHP reserved keyword. 92 */ 93 protected function isReservedKeyword(string $name): bool 94 { 95 return in_array(strtolower($name), self::RESERVED_KEYWORDS, true); 96 } 97 98 /** 99 * Convert to PascalCase. 100 */ 101 public function toPascalCase(string $string): string 102 { 103 // Split on hyphens, underscores, or existing camelCase boundaries 104 $words = preg_split('/[-_\s]+|(?=[A-Z])/', $string); 105 106 if ($words === false) { 107 $words = [$string]; 108 } 109 110 // Capitalize first letter of each word 111 $words = array_map(fn ($word) => ucfirst(strtolower($word)), $words); 112 113 return implode('', $words); 114 } 115 116 /** 117 * Convert to camelCase. 118 */ 119 public function toCamelCase(string $string): string 120 { 121 $pascalCase = $this->toPascalCase($string); 122 123 return lcfirst($pascalCase); 124 } 125 126 /** 127 * Convert to snake_case. 128 */ 129 public function toSnakeCase(string $string): string 130 { 131 // Insert underscore before capital letters, except at the start 132 $snake = preg_replace('/(?<!^)[A-Z]/', '_$0', $string); 133 134 if ($snake === null) { 135 return strtolower($string); 136 } 137 138 return strtolower($snake); 139 } 140 141 /** 142 * Convert to kebab-case. 143 */ 144 public function toKebabCase(string $string): string 145 { 146 return str_replace('_', '-', $this->toSnakeCase($string)); 147 } 148 149 /** 150 * Pluralize a word (simple English rules). 151 */ 152 public function pluralize(string $word): string 153 { 154 if (str_ends_with($word, 'y')) { 155 return substr($word, 0, -1).'ies'; 156 } 157 158 if (str_ends_with($word, 's') || str_ends_with($word, 'x') || str_ends_with($word, 'ch') || str_ends_with($word, 'sh')) { 159 return $word.'es'; 160 } 161 162 return $word.'s'; 163 } 164 165 /** 166 * Singularize a word (simple English rules). 167 */ 168 public function singularize(string $word): string 169 { 170 if (str_ends_with($word, 'ies')) { 171 return substr($word, 0, -3).'y'; 172 } 173 174 if (str_ends_with($word, 'ses') || str_ends_with($word, 'xes') || str_ends_with($word, 'ches') || str_ends_with($word, 'shes')) { 175 return substr($word, 0, -2); 176 } 177 178 if (str_ends_with($word, 's') && ! str_ends_with($word, 'ss')) { 179 return substr($word, 0, -1); 180 } 181 182 return $word; 183 } 184 185 /** 186 * Get the base namespace. 187 */ 188 public function getBaseNamespace(): string 189 { 190 return $this->baseNamespace; 191 } 192 193 /** 194 * Set the base namespace. 195 */ 196 public function setBaseNamespace(string $namespace): void 197 { 198 $this->baseNamespace = rtrim($namespace, '\\'); 199 } 200}