From 153732cf28cc22251356c67ba35154b2a3b02c7c Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:06:57 +0300 Subject: [PATCH 01/10] refactor: Use of Lines Instead of String Concat --- WebFiori/Framework/Writers/ClassWriter.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index ed7675442..362c3cf96 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -27,7 +27,7 @@ abstract class ClassWriter { * * @since 1.0 */ - private $classAsStr; + private $classLines; /** * The name of the class that will be created. * @@ -405,13 +405,13 @@ public function setSuffix(string $classNameSuffix) : bool { public function writeClass() { $classFile = new File($this->getName().'.php', $this->getPath()); $classFile->remove(); - $this->classAsStr = ''; + $this->classLines = []; $this->writeNsDeclaration(); $this->writeUseStatements(); $this->writeClassComment(); $this->writeClassDeclaration(); $this->writeClassBody(); - $classFile->setRawData($this->classAsStr); + $classFile->setRawData(implode("\n", $this->classLines)); $classFile->write(false, true); } public abstract function writeClassBody(); @@ -446,7 +446,7 @@ public function writeUseStatements() { } private function a($str, $tapsCount) { $tabStr = str_repeat(' ', $tapsCount); - $this->classAsStr .= $tabStr.$str."\n"; + $this->classLines[] = $tabStr.$str; } private function fixClassName($className) { $classSuffix = $this->getSuffix(); From 18da8fad4602018a62724463acf587a682ed4773 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:14:02 +0300 Subject: [PATCH 02/10] refactor: Added Code Normalization --- WebFiori/Framework/Writers/ClassWriter.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index 362c3cf96..b7c57de3f 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -411,7 +411,7 @@ public function writeClass() { $this->writeClassComment(); $this->writeClassDeclaration(); $this->writeClassBody(); - $classFile->setRawData(implode("\n", $this->classLines)); + $classFile->setRawData(implode("\n", $this->normalizeCode($this->classLines))); $classFile->write(false, true); } public abstract function writeClassBody(); @@ -448,7 +448,23 @@ private function a($str, $tapsCount) { $tabStr = str_repeat(' ', $tapsCount); $this->classLines[] = $tabStr.$str; } - private function fixClassName($className) { + private function normalizeCode(array $lines) : array { + $normalized = []; + $prevLineEmpty = false; + + foreach ($lines as $line) { + $isEmpty = trim($line) === ''; + + if ($isEmpty && $prevLineEmpty) { + continue; + } + + $normalized[] = $line; + $prevLineEmpty = $isEmpty; + } + + return $normalized; + } private function fixClassName($className) { $classSuffix = $this->getSuffix(); if ($classSuffix == '') { From 4d486b27446d6d6e53a0d2e54a23e09b8d6e40bc Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:25:43 +0300 Subject: [PATCH 03/10] feat: Add New Method" `method` --- WebFiori/Framework/Writers/ClassWriter.php | 58 ++++++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index b7c57de3f..0d9d23f48 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -139,24 +139,62 @@ public function append($strOrArr, $tabsCount = 0) { * it. */ public function f($funcName, $argsArr = [], ?string $returns = null) { + return $this->method($funcName, $argsArr, $returns); + } + /** + * Adds method definition with full control over modifiers. + * + * @param string $funcName Method name + * @param array $argsArr Arguments [name => type] + * @param string|null $returns Return type + * @param string $visibility Visibility: 'public', 'protected', 'private' + * @param bool $isStatic Is static method + * @param bool $isAbstract Is abstract method + * @param bool $isFinal Is final method + * + * @return string Method signature + */ + public function method( + string $funcName, + array $argsArr = [], + ?string $returns = null, + string $visibility = 'public', + bool $isStatic = false, + bool $isAbstract = false, + bool $isFinal = false + ) : string { + $modifiers = []; + + if ($isFinal) { + $modifiers[] = 'final'; + } + if ($isAbstract) { + $modifiers[] = 'abstract'; + } + + $modifiers[] = $visibility; + + if ($isStatic) { + $modifiers[] = 'static'; + } + + $signature = implode(' ', $modifiers) . ' function ' . $funcName; + $argsPart = '('; - foreach ($argsArr as $argName => $argType) { if (strlen($argsPart) != 1) { - $argsPart .= ', '.$argType.' $'.$argName; - continue; + $argsPart .= ', '; } - $argsPart .= $argType.' $'.$argName; + $argsPart .= $argType . ' $' . $argName; } $argsPart .= ')'; - + if ($returns !== null) { - $argsPart .= ' : '.$returns; + $argsPart .= ' : ' . $returns; } - - return 'public function '.$funcName.$argsPart.' {'; - } - /** + + return $signature . $argsPart . ($isAbstract ? ';' : ' {'); + } /** * Returns the absolute path of the class that will be created. * * @return string The absolute path of the file that holds class information. From bebe151ebf59b7612b4f064c5dfddcd926e7fd82 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:30:26 +0300 Subject: [PATCH 04/10] feat: Add `property` and `constant` --- WebFiori/Framework/Writers/ClassWriter.php | 61 +++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index 0d9d23f48..c48b15bbf 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -194,7 +194,66 @@ public function method( } return $signature . $argsPart . ($isAbstract ? ';' : ' {'); - } /** + } + /** + * Generate a property declaration. + * + * @param string $name Property name + * @param string $visibility Visibility: 'public', 'protected', 'private' + * @param string|null $type Property type + * @param string|null $defaultValue Default value as string + * @param bool $isStatic Is static property + * @param bool $isReadonly Is readonly property (PHP 8.1+) + * + * @return string Property declaration + */ + public function property( + string $name, + string $visibility = 'private', + ?string $type = null, + ?string $defaultValue = null, + bool $isStatic = false, + bool $isReadonly = false + ) : string { + $modifiers = [$visibility]; + + if ($isReadonly) { + $modifiers[] = 'readonly'; + } + if ($isStatic) { + $modifiers[] = 'static'; + } + + $declaration = implode(' ', $modifiers); + + if ($type !== null) { + $declaration .= ' ' . $type; + } + + $declaration .= ' $' . $name; + + if ($defaultValue !== null) { + $declaration .= ' = ' . $defaultValue; + } + + return $declaration . ';'; + } + /** + * Generate a constant declaration. + * + * @param string $name Constant name + * @param string $value Constant value as string + * @param string $visibility Visibility: 'public', 'protected', 'private' + * + * @return string Constant declaration + */ + public function constant( + string $name, + string $value, + string $visibility = 'public' + ) : string { + return $visibility . ' const ' . $name . ' = ' . $value . ';'; + }/** * Returns the absolute path of the class that will be created. * * @return string The absolute path of the file that holds class information. From 0eb7760ea9107f5a004f30e2b2d0b5526e3eb9ac Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:38:53 +0300 Subject: [PATCH 05/10] feat: Doc-block Builder --- WebFiori/Framework/Writers/ClassWriter.php | 13 +- .../Framework/Writers/DocblockBuilder.php | 158 ++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 WebFiori/Framework/Writers/DocblockBuilder.php diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index c48b15bbf..c09113c64 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -253,7 +253,18 @@ public function constant( string $visibility = 'public' ) : string { return $visibility . ' const ' . $name . ' = ' . $value . ';'; - }/** + } + /** + * Start building a docblock. + * + * @param string $description Main description + * + * @return DocblockBuilder + */ + public function docblock(string $description = '') : DocblockBuilder { + return new DocblockBuilder($this, $description); + } + /** * Returns the absolute path of the class that will be created. * * @return string The absolute path of the file that holds class information. diff --git a/WebFiori/Framework/Writers/DocblockBuilder.php b/WebFiori/Framework/Writers/DocblockBuilder.php new file mode 100644 index 000000000..d763b7abe --- /dev/null +++ b/WebFiori/Framework/Writers/DocblockBuilder.php @@ -0,0 +1,158 @@ +writer = $writer; + $this->description = $description; + } + + /** + * Add a parameter to the docblock. + * + * @param string $type Parameter type + * @param string $name Parameter name (without $) + * @param string $desc Optional description + * + * @return DocblockBuilder + */ + public function param(string $type, string $name, string $desc = '') : self { + $this->params[] = ['type' => $type, 'name' => $name, 'desc' => $desc]; + return $this; + } + + /** + * Add a return tag to the docblock. + * + * @param string $type Return type + * @param string $desc Optional description + * + * @return DocblockBuilder + */ + public function returns(string $type, string $desc = '') : self { + $this->return = ['type' => $type, 'desc' => $desc]; + return $this; + } + + /** + * Add a custom tag to the docblock. + * + * @param string $name Tag name (without @) + * @param string $value Optional tag value + * + * @return DocblockBuilder + */ + public function tag(string $name, string $value = '') : self { + $this->tags[] = ['name' => $name, 'value' => $value]; + return $this; + } + + /** + * Add @throws tag. + * + * @param string $exception Exception class name + * @param string $desc Optional description + * + * @return DocblockBuilder + */ + public function throws(string $exception, string $desc = '') : self { + return $this->tag('throws', $exception . ($desc ? ' ' . $desc : '')); + } + + /** + * Add @deprecated tag. + * + * @param string $message Optional deprecation message + * + * @return DocblockBuilder + */ + public function deprecated(string $message = '') : self { + return $this->tag('deprecated', $message); + } + + /** + * Add @since tag. + * + * @param string $version Version number + * + * @return DocblockBuilder + */ + public function since(string $version) : self { + return $this->tag('since', $version); + } + + /** + * Build and append the docblock to the class writer. + * + * @param int $indent Indentation level (number of tabs) + * + * @return array The generated docblock lines + */ + public function build(int $indent = 1) : array { + $lines = ['/**']; + + if ($this->description) { + foreach (explode("\n", $this->description) as $line) { + $lines[] = ' * ' . $line; + } + if (!empty($this->params) || $this->return || !empty($this->tags)) { + $lines[] = ' *'; + } + } + + foreach ($this->params as $param) { + $line = ' * @param ' . $param['type'] . ' $' . $param['name']; + if ($param['desc']) { + $line .= ' ' . $param['desc']; + } + $lines[] = $line; + } + + if ($this->return) { + $line = ' * @return ' . $this->return['type']; + if ($this->return['desc']) { + $line .= ' ' . $this->return['desc']; + } + $lines[] = $line; + } + + foreach ($this->tags as $tag) { + $line = ' * @' . $tag['name']; + if ($tag['value']) { + $line .= ' ' . $tag['value']; + } + $lines[] = $line; + } + + $lines[] = ' */'; + + $this->writer->append($lines, $indent); + return $lines; + } +} From ff37c27169234495d3fe8abc1a318ca039521535 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:46:22 +0300 Subject: [PATCH 06/10] feat: Added `getCode` --- WebFiori/Framework/Writers/ClassWriter.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index c09113c64..ee08bb004 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -513,16 +513,23 @@ public function setSuffix(string $classNameSuffix) : bool { public function writeClass() { $classFile = new File($this->getName().'.php', $this->getPath()); $classFile->remove(); + $classFile->setRawData($this->getCode()); + $classFile->write(false, true); + } + /** + * Generate the class code without writing to disk. + * + * @return string The generated class code + */ + public function getCode() : string { $this->classLines = []; $this->writeNsDeclaration(); $this->writeUseStatements(); $this->writeClassComment(); $this->writeClassDeclaration(); $this->writeClassBody(); - $classFile->setRawData(implode("\n", $this->normalizeCode($this->classLines))); - $classFile->write(false, true); - } - public abstract function writeClassBody(); + return implode("\n", $this->normalizeCode($this->classLines)); + } public abstract function writeClassBody(); /** * Writes the top section of the class that contains class comment. */ From 9e11d2be039a04ea2b0fa6797cd463e03b146584 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 20 Jan 2026 23:56:45 +0300 Subject: [PATCH 07/10] feat: Attributes --- WebFiori/Framework/Writers/ClassWriter.php | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index ee08bb004..64591e300 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -264,6 +264,95 @@ public function constant( public function docblock(string $description = '') : DocblockBuilder { return new DocblockBuilder($this, $description); } + /** + /** + * Add an attribute for a class. + * + * @param string $name Attribute name (without #) + * @param array $params Attribute parameters + * @param int $indent Indentation level + * + * @return $this For chaining + */ + public function classAttribute(string $name, array $params = [], int $indent = 0) { + $this->append($this->formatAttribute($name, $params), $indent); + return $this; + } + /** + * Add an attribute for a property. + * + * @param string $name Attribute name (without #) + * @param array $params Attribute parameters + * @param int $indent Indentation level + * + * @return $this For chaining + */ + public function propertyAttribute(string $name, array $params = [], int $indent = 1) { + $this->append($this->formatAttribute($name, $params), $indent); + return $this; + } + /** + * Add an attribute for a method. + * + * @param string $name Attribute name (without #) + * @param array $params Attribute parameters + * @param int $indent Indentation level + * + * @return $this For chaining + */ + public function methodAttribute(string $name, array $params = [], int $indent = 1) { + $this->append($this->formatAttribute($name, $params), $indent); + return $this; + } + /** + * Format an attribute string. + * + * @param string $name Attribute name + * @param array $params Attribute parameters + * + * @return string Formatted attribute + */ + private function formatAttribute(string $name, array $params = []) : string { + $attr = '#[' . $name; + + if (!empty($params)) { + $args = []; + foreach ($params as $key => $value) { + if (is_int($key)) { + $args[] = $this->formatAttributeValue($value); + } else { + $args[] = $key . ': ' . $this->formatAttributeValue($value); + } + } + $attr .= '(' . implode(', ', $args) . ')'; + } + + $attr .= ']'; + return $attr; + } + /** + * Format a value for attribute parameters. + * + * @param mixed $value The value to format + * + * @return string Formatted value + */ + private function formatAttributeValue($value) : string { + if (is_string($value)) { + return "'" . addslashes($value) . "'"; + } + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + if (is_array($value)) { + $items = array_map([$this, 'formatAttributeValue'], $value); + return '[' . implode(', ', $items) . ']'; + } + if (is_null($value)) { + return 'null'; + } + return (string)$value; + } /** * Returns the absolute path of the class that will be created. * From 57656d7029baa4199e5f7b307d8bbea6a79767d2 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 21 Jan 2026 00:00:05 +0300 Subject: [PATCH 08/10] refactor: Throw Exceptions on Errors --- WebFiori/Framework/Writers/ClassWriter.php | 46 ++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index 64591e300..28d958672 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -525,16 +525,18 @@ public function removeUseStatement(string $classToRemove) { * @return boolean If the name is successfully set, the method will return true. * Other than that, false is returned. */ - public function setClassName(string $name) : bool { + public function setClassName(string $name) { $trimmed = trim($name); - if (self::isValidClassName($trimmed)) { - $this->className = $this->fixClassName($trimmed); - - return true; + if (!self::isValidClassName($trimmed)) { + throw new \InvalidArgumentException( + "Invalid class name '$name'. Class names must start with a letter or underscore, " . + "followed by letters, numbers, or underscores." + ); } - return false; + $this->className = $this->fixClassName($trimmed); + return $this; } /** @@ -549,11 +551,14 @@ public function setNamespace(string $namespace) { $trimmed = trim($namespace, ' '); if (!self::isValidNamespace($trimmed)) { - return false; + throw new \InvalidArgumentException( + "Invalid namespace '$namespace'. Namespaces must contain valid PHP identifiers " . + "separated by backslashes." + ); } + $this->ns = $trimmed[0] == '\\' ? substr($trimmed, 1) : $trimmed; - - return true; + return $this; } /** * Sets the location at which the class will be created on. @@ -563,15 +568,15 @@ public function setNamespace(string $namespace) { * @return boolean If the path is successfully set, the method will return true. * Other than that, false is returned. */ - public function setPath(string $path) : bool { + public function setPath(string $path) { $trimmed = trim($path); if (strlen($trimmed) == 0) { - return false; + throw new \InvalidArgumentException("Path cannot be empty."); } + $this->path = str_replace('\\', DS, str_replace('/', DS, $trimmed)); - - return true; + return $this; } /** * Sets a string as a suffix to the class name. @@ -581,15 +586,16 @@ public function setPath(string $path) : bool { * * @return bool If set, the method will return true. False otherises. */ - public function setSuffix(string $classNameSuffix) : bool { - if (self::isValidClassName($classNameSuffix)) { - $this->suffix = $classNameSuffix; - $this->className = $this->fixClassName($this->className); - - return true; + public function setSuffix(string $classNameSuffix) { + if (!self::isValidClassName($classNameSuffix)) { + throw new \InvalidArgumentException( + "Invalid suffix '$classNameSuffix'. Suffix must be a valid class name." + ); } - return false; + $this->suffix = $classNameSuffix; + $this->className = $this->fixClassName($this->className); + return $this; } /** * Write the new class to a .php file. From f54ed91850136e334b6092e27c797ace2cf6a63c Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 21 Jan 2026 00:02:02 +0300 Subject: [PATCH 09/10] feat: Code Reuse Helpers --- WebFiori/Framework/Writers/ClassWriter.php | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index 28d958672..930592f10 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -354,6 +354,114 @@ private function formatAttributeValue($value) : string { return (string)$value; } /** + * Write a standard constructor method. + * + * @param array $params Constructor parameters [name => type] + * @param string|array $body Constructor body (string or array of lines) + * @param string $description Optional docblock description + * @param int $indent Indentation level + */ + protected function writeConstructor( + array $params = [], + $body = '', + string $description = 'Creates new instance of the class.', + int $indent = 1 + ) { + $this->docblock($description)->build($indent); + $this->append($this->method('__construct', $params), $indent); + + if (is_array($body)) { + $this->append($body, $indent + 1); + } else if ($body) { + $this->append($body, $indent + 1); + } + + $this->append('}', $indent); + } + /** + * Write a standard getter method. + * + * @param string $property Property name + * @param string $type Return type + * @param string $description Optional description + * @param int $indent Indentation level + */ + protected function writeGetter( + string $property, + string $type, + string $description = '', + int $indent = 1 + ) { + $methodName = 'get' . ucfirst($property); + + $this->docblock($description ?: "Returns the value of $property.") + ->returns($type) + ->build($indent); + + $this->append($this->method($methodName, [], $type), $indent); + $this->append("return \$this->$property;", $indent + 1); + $this->append('}', $indent); + } + /** + * Write a standard setter method. + * + * @param string $property Property name + * @param string $type Parameter type + * @param string $description Optional description + * @param int $indent Indentation level + */ + protected function writeSetter( + string $property, + string $type, + string $description = '', + int $indent = 1 + ) { + $methodName = 'set' . ucfirst($property); + + $this->docblock($description ?: "Sets the value of $property.") + ->param($type, $property) + ->returns('void') + ->build($indent); + + $this->append($this->method($methodName, [$property => $type], 'void'), $indent); + $this->append("\$this->$property = \$$property;", $indent + 1); + $this->append('}', $indent); + } + /** + * Write both getter and setter for a property. + * + * @param string $property Property name + * @param string $type Property type + * @param int $indent Indentation level + */ + protected function writeGetterSetter(string $property, string $type, int $indent = 1) { + $this->writeGetter($property, $type, '', $indent); + $this->writeSetter($property, $type, '', $indent); + } + /** + * Write an empty method stub with TODO comment. + * + * @param string $methodName Method name + * @param array $params Method parameters [name => type] + * @param string|null $returns Return type + * @param string $description Method description + * @param int $indent Indentation level + */ + protected function writeMethodStub( + string $methodName, + array $params = [], + ?string $returns = null, + string $description = '', + int $indent = 1 + ) { + if ($description) { + $this->docblock($description)->build($indent); + } + + $this->append($this->method($methodName, $params, $returns), $indent); + $this->append('//TODO: Implement this method.', $indent + 1); + $this->append('}', $indent); + } /** * Returns the absolute path of the class that will be created. * * @return string The absolute path of the file that holds class information. From d3b24077f1652396226152581f043d8bc323c15a Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 21 Jan 2026 00:13:32 +0300 Subject: [PATCH 10/10] feat: Chaining --- WebFiori/Framework/Writers/ClassWriter.php | 38 +++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/WebFiori/Framework/Writers/ClassWriter.php b/WebFiori/Framework/Writers/ClassWriter.php index 930592f10..29b694e32 100644 --- a/WebFiori/Framework/Writers/ClassWriter.php +++ b/WebFiori/Framework/Writers/ClassWriter.php @@ -102,6 +102,8 @@ public function addUseStatement($classesToUse) { } /** * Appends a string or array of strings to the string that represents the + + return $this; * body of the class. * * @param string $strOrArr The string that will be appended. At the end of the string @@ -116,12 +118,13 @@ public function append($strOrArr, $tabsCount = 0) { if (gettype($strOrArr) != 'array') { $this->a($strOrArr, $tabsCount); - return; + return $this; } foreach ($strOrArr as $str) { $this->a($str, $tabsCount); } + return $this; } /** * Adds method definition to the class. @@ -152,7 +155,7 @@ public function f($funcName, $argsArr = [], ?string $returns = null) { * @param bool $isAbstract Is abstract method * @param bool $isFinal Is final method * - * @return string Method signature + * @return $this For chaining */ public function method( string $funcName, @@ -162,7 +165,7 @@ public function method( bool $isStatic = false, bool $isAbstract = false, bool $isFinal = false - ) : string { + ) { $modifiers = []; if ($isFinal) { @@ -193,7 +196,8 @@ public function method( $argsPart .= ' : ' . $returns; } - return $signature . $argsPart . ($isAbstract ? ';' : ' {'); + $this->append($signature . $argsPart . ($isAbstract ? ';' : ' {'), 1); + return $this; } /** * Generate a property declaration. @@ -205,7 +209,9 @@ public function method( * @param bool $isStatic Is static property * @param bool $isReadonly Is readonly property (PHP 8.1+) * - * @return string Property declaration + * @param int|null $indent If provided, appends to class and returns $this for chaining + * + * @return string|$this Property declaration string, or $this if $indent is provided */ public function property( string $name, @@ -214,7 +220,7 @@ public function property( ?string $defaultValue = null, bool $isStatic = false, bool $isReadonly = false - ) : string { + ) { $modifiers = [$visibility]; if ($isReadonly) { @@ -236,7 +242,8 @@ public function property( $declaration .= ' = ' . $defaultValue; } - return $declaration . ';'; + $this->append($declaration . ';', 1); + return $this; } /** * Generate a constant declaration. @@ -245,14 +252,24 @@ public function property( * @param string $value Constant value as string * @param string $visibility Visibility: 'public', 'protected', 'private' * - * @return string Constant declaration + * @return $this For chaining */ public function constant( string $name, string $value, string $visibility = 'public' - ) : string { - return $visibility . ' const ' . $name . ' = ' . $value . ';'; + ) { + $this->append($visibility . ' const ' . $name . ' = ' . $value . ';', 1); + return $this; + } + /** + * Add an empty line (fluent version). + * + * @return $this For chaining + */ + public function addEmptyLine() { + $this->append(''); + return $this; } /** * Start building a docblock. @@ -264,7 +281,6 @@ public function constant( public function docblock(string $description = '') : DocblockBuilder { return new DocblockBuilder($this, $description); } - /** /** * Add an attribute for a class. *