Giter Site home page Giter Site logo

nette / php-generator Goto Github PK

View Code? Open in Web Editor NEW
2.1K 63.0 132.0 895 KB

🐘 Generates neat PHP code for you. Supports new PHP 8.3 features.

Home Page: https://doc.nette.org/php-generator

License: Other

PHP 100.00%
nette nette-framework php-generator php code-generator

php-generator's Introduction

Nette PHP Generator

Latest Stable Version Downloads this Month

 

Are you looking for a tool to generate PHP code for classes, functions, or complete PHP files?

✅ Supports all the latest PHP features like enums, attributes, etc.
✅ Allows you to easily modify existing classes
✅ Output compliant with PSR-12 / PER coding style
✅ Highly mature, stable, and widely used library

 

Installation

Download and install the library using the Composer tool:

composer require nette/php-generator

PhpGenerator 4.1 is compatible with PHP 8.0 to 8.4. Documentation can be found on the library's website.

 

Do you like PHP Generator? Are you looking forward to the new features?

Buy me a coffee

Thank you!

 

Classes

Let's start with an example of creating a class using ClassType:

$class = new Nette\PhpGenerator\ClassType('Demo');

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Class description.\nSecond line\n")
	->addComment('@property-read Nette\Forms\Form $form');

// generate code simply by typecasting to string or using echo:
echo $class;

This will return:

/**
 * Class description
 * Second line
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

To generate the code, you can also use a so-called printer, which, unlike echo $class, can be further configured:

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);

You can add constants (class Constant) and properties (class Property):

$class->addConstant('ID', 123)
	->setProtected() // constant visibility
	->setType('int')
	->setFinal();

$class->addProperty('items', [1, 2, 3])
	->setPrivate() // or setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // outputs '= null'

This will generate:

final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

public ?array $list = null;

And you can add methods:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // return types for methods
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

The result is:

/**
 * Count it.
 */
final protected function count(array &$items = []): ?int
{
	return count($items ?: $this->items);
}

Promoted parameters introduced in PHP 8.0 can be passed to the constructor:

$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
	->setPrivate();

The result is:

public function __construct(
	public $name,
	private $args = [],
) {
}

Readonly properties and classes be marked using the setReadOnly() function.


If an added property, constant, method, or parameter already exists, an exception is thrown.

Class members can be removed using removeProperty(), removeConstant(), removeMethod(), or removeParameter().

You can also add existing Method, Property, or Constant objects to the class:

$method = new Nette\PhpGenerator\Method('getHandle');
$property = new Nette\PhpGenerator\Property('handle');
$const = new Nette\PhpGenerator\Constant('ROLE');

$class = (new Nette\PhpGenerator\ClassType('Demo'))
	->addMember($method)
	->addMember($property)
	->addMember($const);

You can also clone existing methods, properties, and constants under a different name using cloneWithName():

$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);

 

Interfaces or Traits

You can create interfaces and traits (classes InterfaceType and TraitType):

$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');

Using a trait:

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
	->addResolution('sayHello as protected')
	->addComment('@use MyTrait<Foo>');
echo $class;

The result is:

class Demo
{
	use SmartObject;
	/** @use MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}

 

Enums

You can easily create enums introduced in PHP 8.1 like this (class EnumType):

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');

echo $enum;

The result is:

enum Suit
{
	case Clubs;
	case Diamonds;
	case Hearts;
	case Spades;
}

You can also define scalar equivalents and create a "backed" enum:

$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');

For each case, you can add a comment or attributes using addComment() or addAttribute().

 

Anonymous Classes

Pass null as the name, and you have an anonymous class:

$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';

The result is:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

 

Global Functions

The code for functions is generated by the class GlobalFunction:

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

The result is:

function foo($a, $b)
{
	return $a + $b;
}

 

Anonymous Functions

The code for anonymous functions is generated by the class Closure:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

The result is:

function ($a, $b) use (&$c) {
	return $a + $b;
}

 

Short Arrow Functions

You can also output a short anonymous function using the printer:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');

echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);

The result is:

fn($a, $b) => $a + $b

 

Method and Function Signatures

Methods are represented by the class Method. You can set visibility, return value, add comments, attributes, etc.:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');

Individual parameters are represented by the class Parameter. Again, you can set all conceivable properties:

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

// function count(&$items = [])

To define the so-called variadics parameters (or also the splat, spread, ellipsis, unpacking or three dots operator), use setVariadics():

$method = $class->addMethod('count');
$method->setVariadics(true);
$method->addParameter('items');

This generates:

function count(...$items)
{
}

 

Method and Function Bodies

The body can be passed all at once to the setBody() method or gradually (line by line) by repeatedly calling addBody():

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;

The result is:

function foo()
{
	$a = rand(10, 20);
	return $a;
}

You can use special placeholders for easy variable insertion.

Simple placeholders ?

$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;

The result is:

function foo()
{
	return substr('any string', 3);
}

Placeholder for variadic ...?

$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;

The result is:

function foo()
{
	myfunc(1, 2, 3);
}

You can also use named parameters for PHP 8 with ...?:

$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);

// myfunc(foo: 1, bar: true);

The placeholder is escaped with a backslash \?

$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;

The result is:

function foo($a)
{
	return $a ? 10 : 3;
}

 

Printer and PSR Compliance

The Printer class is used for generating PHP code:

$class = new Nette\PhpGenerator\ClassType('Demo');
// ...

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // same as: echo $class

It can generate code for all other elements, offering methods like printFunction(), printNamespace(), etc.

There's also the PsrPrinter class, which outputs in accordance with PSR-2 / PSR-12 / PER coding style:

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);

Need custom behavior? Create your own version by inheriting the Printer class. You can reconfigure these variables:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// length of the line after which the line will break
	public int $wrapLength = 120;
	// indentation character, can be replaced with a sequence of spaces
	public string $indentation = "\t";
	// number of blank lines between properties
	public int $linesBetweenProperties = 0;
	// number of blank lines between methods
	public int $linesBetweenMethods = 2;
	// number of blank lines between 'use statements' groups for classes, functions, and constants
	public int $linesBetweenUseTypes = 0;
	// position of the opening curly brace for functions and methods
	public bool $bracesOnNextLine = true;
	// place one parameter on one line, even if it has an attribute or is supported
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator between the right parenthesis and return type of functions and methods
	public string $returnTypeColon = ': ';
}

How and why does the standard Printer differ from PsrPrinter? Why isn't there just one printer, the PsrPrinter, in the package?

The standard Printer formats the code as we do throughout Nette. Since Nette was established much earlier than PSR, and also because PSR took years to deliver standards on time, sometimes even several years after introducing a new feature in PHP, it resulted in a coding standard that differs in a few minor aspects. The major difference is the use of tabs instead of spaces. We know that by using tabs in our projects, we allow for width customization, which is essential for people with visual impairments. An example of a minor difference is placing the curly brace on a separate line for functions and methods, always. The PSR recommendation seems illogical to us and leads to reduced code clarity.

 

Types

Every type or union/intersection type can be passed as a string; you can also use predefined constants for native types:

use Nette\PhpGenerator\Type;

$member->setType('array'); // or Type::Array;
$member->setType('?array'); // or Type::nullable(Type::Array);
$member->setType('array|string'); // or Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
$member->setType(null); // removes the type

The same applies to the setReturnType() method.

 

Literals

Using Literal, you can pass any PHP code, for example, for default property values or parameters, etc:

use Nette\PhpGenerator\Literal;

$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('foo', new Literal('Iterator::SELF_FIRST'));

$class->addMethod('bar')
	->addParameter('id', new Literal('1 + 2'));

echo $class;

Result:

class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}

You can also pass parameters to Literal and have them formatted into valid PHP code using placeholders:

new Literal('substr(?, ?)', [$a, $b]);
// generates for example: substr('hello', 5);

A literal representing the creation of a new object can easily be generated using the new method:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generates for example: new Demo(10, foo: 20)

 

Attributes

With PHP 8, you can add attributes to all classes, methods, properties, constants, enum cases, functions, closures, and parameters. You can also use literals as parameter values.

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addAttribute('Table', [
	'name' => 'user',
	'constraints' => [
		Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]),
	],
]);

$class->addProperty('list')
	->addAttribute('Deprecated');

$method = $class->addMethod('count')
	->addAttribute('Foo\Cached', ['mode' => true]);

$method->addParameter('items')
	->addAttribute('Bar');

echo $class;

Result:

#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
	#[Deprecated]
	public $list;


	#[Foo\Cached(mode: true)]
	public function count(
		#[Bar]
		$items,
	) {
	}
}

 

Namespace

Classes, traits, interfaces, and enums (hereafter referred to as classes) can be grouped into namespaces represented by the PhpNamespace class:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

// create new classes in the namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// or insert an existing class into the namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

If the class already exists, an exception is thrown.

You can define use clauses:

// use Http\Request;
$namespace->addUse(Http\Request::class);
// use Http\Request as HttpReq;
$namespace->addUse(Http\Request::class, 'HttpReq');
// use function iter\range;
$namespace->addUseFunction('iter\range');

To simplify a fully qualified class, function, or constant name based on defined aliases, use the simplifyName method:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is the current namespace
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', due to the defined use-statement

Conversely, you can convert a simplified class, function, or constant name back to a fully qualified name using the resolveName method:

echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'

 

Class Names Resolving

When a class is part of a namespace, it's rendered slightly differently: all types (e.g., type hints, return types, parent class name, implemented interfaces, used traits, and attributes) are automatically resolved (unless you turn it off, see below). This means you must use fully qualified class names in definitions, and they will be replaced with aliases (based on use clauses) or fully qualified names in the resulting code:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // will be simplified to A
	->addTrait('Bar\AliasedClass'); // will be simplified to AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // we manually simplify in comments
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // will be translated to \Bar\OtherClass

echo $namespace;

// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Result:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}

Auto-resolving can be turned off this way:

$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);

 

PHP Files

Classes, functions, and namespaces can be grouped into PHP files represented by the PhpFile class:

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adds declare(strict_types=1)

$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// or
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');

echo $file;

// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Result:

<?php

/**
 * This file is auto-generated.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Please note: No additional code can be added to the files outside of functions and classes.

 

Generating from Existing Ones

In addition to being able to model classes and functions using the API described above, you can also have them automatically generated using existing ones:

// creates a class identical to the PDO class
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// creates a function identical to the trim() function
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// creates a closure based on the provided one
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

By default, function and method bodies are empty. If you also want to load them, use this method (requires the nikic/php-parser package to be installed):

$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);

$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);

 

Loading from PHP Files

You can also load functions, classes, interfaces, and enums directly from a string containing PHP code. For example, to create a ClassType object:

$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
	<?php

	class Demo
	{
		public $foo;
	}
	XX);

When loading classes from PHP code, single-line comments outside method bodies are ignored (e.g., for properties, etc.), as this library doesn't have an API to work with them.

You can also directly load an entire PHP file, which can contain any number of classes, functions, or even namespaces:

$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));

The file's initial comment and strict_types declaration are also loaded. However, all other global code is ignored.

It requires nikic/php-parser to be installed.

(If you need to manipulate global code in files or individual statements in method bodies, it's better to use the nikic/php-parser library directly.)

 

Variable Dumping

The Dumper class converts a variable into parseable PHP code. It provides a better and clearer output than the standard var_export() function.

$dumper = new Nette\PhpGenerator\Dumper;

$var = ['a', 'b', 123];

echo $dumper->dump($var); // outputs ['a', 'b', 123]

php-generator's People

Contributors

19bischof avatar adaamz avatar coffelius avatar dg avatar djaf77 avatar fprochazka avatar frosty22 avatar gromnan avatar iggyvolz avatar jakubkulhan avatar jantvrdik avatar jeroeny avatar jonsa avatar juniwalk avatar juzna avatar kukulich avatar majkl578 avatar pavelkouril avatar pierredup avatar pionl avatar sanderdlm avatar sisklu avatar uestla avatar xpavp03 avatar yoosefap avatar zeleznypa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

php-generator's Issues

Add possibility to use constants in attributes

Hi,
Is it possible to add support for constants in attributes arguments?

So generated code may look like this:

#[Argument('name', type: ArgumentTypes::REQUIRED)]

Currently it is possible to do something like [code below] or just use const value:

$class->setAttribute(
    'Argument', 
    [
        'name',
        'type' => 'ArgumentTypes::REQUIRED'
    ]
);

in the result a constant will be wrapped in the quotes and treated as a string

#[Argument('name', type: 'ArgumentTypes::REQUIRED')]

I believe this feature will be highly demanded as more and more libs and frameworks are adding support for PHP 8 attributes. And they already use constants.

Dump double quoted strings

  • Is there a way to dump double quoted strings?

I want to do something like:

return "Something ${value}";

// so php can interpret the variable 'value' and return its value, not the string 'Something ${value}'

Automatically import (`use`) classes to namespace

I'm using this library to generate a lot of PHP classes.

For every returnType, propertyType, argumentType I have to remember to call $namespace->addUse().

It would be really nice if these types could be automatically imported.

For example like this:

$file = new PhpFile();

$ns = $file->addNamespace('My\\App', $autoImport = true);

$class = $ns->addClass('MyClass');

$constructor = $class->addMethod('__construct');

$constructor->addParameter('data')
    ->setType('\My\\App\\Entity\\User');

Would such a change be accepted? Or is there a better way to deal with this?

Nette\PhpGenerator\Parameter havent option/method to detect is Method parameter variadic

Version: 3.3.3

Bug Description

I tried to copy class using reflection then make some changes with method parameters, calling it through different method. So i join params using php native implode() function and failed, because i had some params variadic with ...$param definition in method signature.

I tried to check, but there's no method, will use php reflection method for that at now

Loading a class and continue editing that class.

First of all, great package, amazing really!
I'm trying to use it to build my models, and that works great already.

Creating things like:

public function properties()
	{
		return [
			(new Text)->name('Naam')
				->alias('name')
				->required()
				->placement('left'),
		];
	}

This "new Text" is a type that I choose using a form.
A second time I want to edit this model with that form, I could want to remove a type for instance.
That's fine and possible if I generate the whole class again.

The problem is that I want it to be open for modification.
So if I add a method to it. Let's say:

public function properties()
	{
		return [
			(new Text)->name('Naam')
				->alias('name')
				->required()
				->placement('left'),
		];
	}

public function test()
{
    return 'test';
}

That method is not in the form where I edit the model.
So if I generate it again I can't know if there are methods that need to be preserved.

My feature request therefor is that there is an option to load a full class with everything.
This way I can just edit a method instead of regenerating everything.

Force the rendering of `use`

Hi @nette team,

I am building an ORM (LightQL) for my framework and now I'm creating a CLI tool used to generate PHP entities from a database schema. I'm able to generate the PHP file perfectly through your package but I'm facing a little problem: I'm unable to force the rendering of use.

These use have to be rendered for the entities to work well since even if they are not used in PHP code or even if the class to use believe in the same namespace than the generated one, they are used by annotations in comments and resolved by the LightQL backend (for one-to-one or one-to-many associations for example). Because this goal cannot be achieved now (or maybe I have missed some configurations), generated entities looks very fine but cannot be used without be edited manually...

So I think it could be fine if we have a configuration value used to define if the we want to force the rendering of use (or other objects) or not.

Thanks a lot for this awesome work! 😁

Make space between closing method parentheses and colon configurable

Our coding standards enforces a space after the closing method parentheses and the proceeding colon.
It would super advantageous if this space could be configurable and honored in the printReturnType method.

Currently we have to manually go to all generated files and apply the spacing. As you can imagine this is very monotonous. I'm sure many others could also benefit from this.

I would super happy to contribute a PR if this is taken into consideration.

Parsing a Class doesn't includes method body.

Version: 3.3.4

Bug Description

Using an existing class won't register method bodies at all.

Steps To Reproduce

Create one class with a method body, and then parse it manually.

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();

        //
    }
}
use App\Providers\EventServiceProvider;

$class = \Nette\PhpGenerator\ClassType::from(EventServiceProvider::class);

echo $class->getMethod('boot')->getBody(); // ''

Expected Behavior

use App\Providers\EventServiceProvider;

$class = \Nette\PhpGenerator\ClassType::from(EventServiceProvider::class);

echo $class->getMethod('boot')->getBody(); //  'parent::boot();\n\n//'

Possible Solution

Add this method to retrieve the body inside the \PhpGenerator\Factory::fromMethodReflection:

protected function getMethodBody(\ReflectionMethod $from) : string
{
    $lines = file($from->getFileName());
    $string = '';

    for ($start = $from->getStartLine()+1, $end = $from->getEndLine()-1; $start < $end; $start++) {
        $string .= $lines[$start];
    }

    return $string;
}

From there, the only thing would be to "unpad" each line.

Feature request: Make spacing between class properties configurable

I've noticed that v3.4.0 contains the following change: Printer: one-line properties without comments are not separated using one empty line.

In our codebase we have a code standard where we do have an empty line between properties, regardless of there being a comment or not. Now of course I understand that everyone has their own standards.

So maybe it could be nice if this value is configurable, just like linesBetweenMethods. So we can override it in our Printer.

I hope this is something that can be considered. If it is, I'd be happy to contribute a PR.

Attributes are not loaded correctly

Version: 3.6.4

Bug Description

When loading from the code (PhpFile::fromCode($content)), attribute arguments are not loaded as expected: arguments are "duplicated".
For instance, with this attribute:

#[AnAttribute(foo: 'bar')]

The loaded arguments will be:

$args = ['foo' => new Literal("foo: 'bar'")];

Expected Behavior

The loaded arguments should be:

$args = ['foo' => new Literal("'bar'")];

Possible Solution

The issue comes from the call to getReformattedContents which gets everything inside the attribute as raw. It shouldn't be the case when loading content.

Incorrect method property mixed type generated from reflection

Version: 3.5.1

Bug Description

class SomeClass {
  public function something(mixed $param) {}
}

// ClassType::withBodiesFrom(SomeClass::class) generates:

class SomeClass {
  public function something(?mixed $param) {}
}

which is incorrect as null is implicitly contained in mixed.

image

Wrong wrapping condition in array formater

Version: 3.2.*

Bug Description

Wrapping condition ignore the length of the property name and needed characters $ = []
https://github.com/nette/php-generator/blob/master/src/PhpGenerator/Helpers.php#L97

Steps To Reproduce

Actual generator produce code:

    /** @var int[] */
    public $terminalsValues = ['true' => 3, 'false' => 4, 'null' => 5, '{' => 6, '}' => 7, ',' => 8, ':' => 9, '[' => 10, ']' => 11];

Expected Behavior

Generate code:

    /** @var int[] */
    public $terminalsValues = [
        'true' => 3, 
        'false' => 4, 
        'null' => 5, 
        '{' => 6, 
        '}' => 7, 
        ',' => 8, 
        ':' => 9, 
        '[' => 10, 
        ']' => 11
    ];

Possible Solution

Add possibility to modify Helper::WRAP_LENGTH by custom length that will be filled here by str_len($property->getName()) + 6

How to remove traits from class parsed from reflection?

This is damn awesome work with this library, you saved my life.

Not sure i would need it, but really - there's no deleteTrait() method inside ClassType, currently i made some static facades for my services, and not sure, what if i add some traits to my services - will it be inside created class.

Anyway, THANK YOU.

--------------^ Click "Preview" for a nicer view!
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁.

AddConstant does not validate name.

  • bug report? yes
  • feature request?no
  • version: v2.5.x-dev

Description

Constant names are not validated resulting in invalid PHP files.

Steps To Reproduce

$file = new \Nette\PhpGenerator\PhpFile();
$object = $file->addClass('Tester');
$object->addConstant('test-invalid-name', "value");

3.3.0 contains BC Break

Version: 3.3.0

Bug Description

When updating version 3.2.3 -> 3.3.0, nette/di vendor throws an err:

Fatal error: Uncaught Nette\MemberAccessException: Call to undefined static method Nette\PhpGenerator\Helpers::createObject(). in /Users/paveljanda/www/gamee/apps/admin/vendor/nette/utils/src/Utils/ObjectHelpers.php:78

Nette DIC is creating instances using following call:

Nette\PhpGenerator\Helpers::createObject

Steps To Reproduce

Upgrade nette/php-generator 3.2.3 -> 3.3.0 and try to build a DIC possibly using phpstan command.
PHPstan version: 0.11.19
(Nette\PhpGenerator\Helpers::createObject)

Expected Behavior

No BC Breaks when updating minor version

Possible Solution

Maybe releasing a 4.0.0 with BC breaks or leaving the minor release without BC Breaks

Versions diff: v3.2.3...v3.3.0

ClassType::from() resolves too much interfaces

Version: v3.1.2

Bug Description

When "copying" a class through the static factory method ClassType::from(...), it appears that all parent interfaces are applied, not only the ones directly set on the class.

Steps To Reproduce

  1. Create some class that implements interfaces
class Query implements \ArrayAccess, \Countable, \Iterator
{
    # ...
}
  1. Then create a ClassType instance through:
    $class = ClassType::from('MyClass');
  2. Convert it to a string and take a closer look on the result:
class Query implements \ArrayAccess, \Countable, \Iterator, \Traversable
{
    # ...
}

Expected Behavior

The instance of ClassType should be exactly the same as the original one.
Instead, it implements the parental interface Traversable which then leads to an E_COMPILE_ERROR, due to the interface beeing implemented twice:

Class Melior\Database\Query\Query cannot implement previously implemented interface Traversable

Possible Solution

I don't got any handy solution on this, but it seems as ReflectionClass doesn't care about whether something has been defined directly in the class or has been inherited from parents.
So gathering the interfaces from the ReflectionClass results in all interfaces and their parents, not just the ones directly named behind implements.

How to use ->setType('OtherClass') within the same namespace

Version: 3.4.0

Bug Description

The class then with a backslash reports that the class was not found if you have the same namespace.

Steps To Reproduce

Generates this:
public \OtherClass $class

Expected Behavior

But in the same namespace I need this:
public OtherClass $class;

Loading from code should not use FQCN

Version: 3.6.4

Bug Description

When loading from the code (PhpFile::fromCode($content)), everything is resolved to its FQCN because of the NameResolver visitor.
It is then very difficult to merge new generated code with the loaded one, since the first one is not using FQCN.

Expected Behavior

Loading from code should not use FQCN for its nodes.

Possible Solution

Either do not use NameResolver when loading, or use the originalName attribute of the node instead.

no facility for adding "leafs"

  • bug report? no
  • feature request? yes
  • version: 3.1.0

Description

Specifically, Class created from reflection can't be added to PhpFile. Generally, you should be able to add leafs beyond mere string creations.

Steps To Reproduce

I.E FPhpFile->add(new ClassType), ClassType->add(new Method)

@see SimpleXML

Add use statements to import global functions is not backwards compatible anymore

Since Version: 3.6.2

The Nette\PhpGenerator\PhpNamespace::addUse method can only handle namespaces.

So the following code snippet is not working anymore while it was working in version <= 3.6.1:

<?php

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpNamespace;

require 'vendor/autoload.php';

$class = new ClassType('MyClass', new PhpNamespace('MyNamespace\MySubNamespace'));
$class->getNamespace()->addUse('function strtoupper');

Helpers::createObject() creates public property

Hi. Take this code:

class SomeObject
{
    private $someProperty;

    function __construct($someProperty)
    {
        $this->someProperty = $someProperty;
    }
}

var_dump(\Nette\PhpGenerator\Helpers::createObject('SomeObject', ['someProperty' => 'test']));

Expected result:

object(SomeObject)[2]
  private 'someProperty' => string 'test' (length=4)

Actual result:

object(SomeObject)[2]
  private 'someProperty' => null
  public 'someProperty' => string 'test' (length=4)

Its on master. I've found this bug when I wanted create service in DI extension and used:

$builder->addDefinition($this->prefix('someService'))
    ->setClass('SomeService', ['someProperty' => $value]);

Missing possibility to add variadic parameter to a method

Simple example:

$method = $class->addMethod('count')
                ->addParameter('entities')
                ->setType('object ...');

Would result in the following:

function count(object ... $entities)
{
}

The space between the variadic operator ... and the variable name $entities is not allowed by PSR12 standard.
A method like setVariadic(bool $state = true): self; on the Nette\PhpGenerator\Parameter could help with that.

Functions in namespace

Currently it is only possible to add ClassTypes to namespaces. I would also like to add functions.

Extended interfaces are marked as implemented in ClassType::from

Version: 3.5.4 (also reproduced on master)

Bug Description

When performing a ClassType::from on an interface which extends another interface, the relation is marked as an implements relationship rather than an extends relationship.

Steps To Reproduce

<?php
require_once __DIR__ . "/vendor/autoload.php";
interface a{}
interface b extends a{}
echo \Nette\PhpGenerator\ClassType::from(b::class);

Gives:

interface b implements a
{
}

Expected Behavior

interface b extends a
{
}

Possible Solution

The check here

$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
may need to split based on whether the class is an interface or not - from my understanding interfaces can only extend other interfaces.

Load a class from a file template

Hello
Thank you for this lib and its well designed api.

Is it possible to load a class from a file?

This could avoid loading a class in the current namespace to use reflection.
It could make php tools based on this library possible. Like refactoring tool.
The usecase here is to manipulate files wherever they are, independently from the current execution context.

Something like Factory::fromClassFile

Thanks

Namespace not recognizing boolean and integer as keywords

Version: 3.3.4

Bug Description

When generating a namespace the PhpNamespace contains a constant of primitive types to be considered as "keywords" and thus not to be imported. The boolean keyword is missing from the list (bool is present instead), same goes for integer. The problem with this is, that if you are using reflections to determine type hints the returned names would be the full-length names, e.g. integer and not int, which in turn would generate stuff like getTheValue(): \boolean;.

Force root namespace

Hi
I crate class with namespace and i want to extend from that namespace but not imposible
Because it create backslash before extend class:
image

Proposal: Adding previously created objects to existing ones

Hey there,
I currently use this awesome library to regenerate existing classes with some modifications.
For this reason I'm often forced to to something like this:

$target = ClassType::from($targetClass);

$methods = $target->getMethods();
$newMethods = [];

foreach ($methods as $method) {
    # do some work
    $newMethods[] = $someNewMethod;
}

$target->setMethods(array_merge($methods, $newMethods));

It would be really nice if it would be possible to directly add existing Method or Property instances to an ClassType instance, instead of using this workaround. Currently it's only possible to create new methods or properties through their respective add...() methods as far as I know.
I imagine something like this:

$target = ClassType::from($targetClass);

foreach ($target->getMethods() as $method) {
    # do some work
    $target->addExistingMethod($someNewMethod);
}

Even worse is the process of adding ClassType objects to a PhpFile. For that purpose I had to write a helper method looking like this:

public static function addClassFromTemplate(PhpNamespace &$target, ClassType $tpl): ClassType
{
    return $target->addClass($tpl->getName())
                ->setType($tpl->getType())
                ->setFinal($tpl->isFinal())
                ->setAbstract($tpl->isAbstract())
                ->setExtends($tpl->getExtends())
                ->setImplements($tpl->getImplements())
                ->setTraits($tpl->getTraits())
                ->setConstants($tpl->getConstants())
                ->setProperties($tpl->getProperties())
                ->setMethods($tpl->getMethods())
                ->setComment($tpl->getComment());
}

In this case, it would be even more helpful to add the class with a one-liner.
Let me know what you think about my proposal.

Greetings, MCStreetguy

Feature Request: add PhpNamespace to PhpFile

Description

PhpNamespace classes can't be added to a PhpFile class.

Example

<?php declare(strict_types=1); 

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\PhpNamespace;

$phpFile= new PhpFile();
$namespace = new PhpNamespace('FooBar');
$class = new ClassType('Foo');

$method = $class->addMethod('baz');
$method->setReturnType('FooBar\\Bar');
$method->setBody('return new Bar();');

$namespace->add($class);
$namespace->add(new ClassType('Bar'));

$phpFile->add($namespace);

echo $phpFile;

Expected result:

<?php

namespace FooBar;

class Foo
{
    public function baz(): Bar
    {
        return new Bar();
    }
}

class Bar
{
}

Define PHP version manually

It would be nice to have option to define PHP version support manually as production environment might be different from development and certain syntax might not be supported.

Method's and function's bodies are copied without name resolve

Version: 3.4.0

Bug Description

Class generated using Reflection have fully-qualified names, because Reflection return all names as FQN. But method's and function's bodies are copied without fully-qualified name.
Nikic's NodeVisitor\NameResolver resolve name, but not change node's position in file, which used for copying body.

Steps To Reproduce

We have class for reflection:

namespace Abc;

abstract class MyClass
{
	function method(AnotherClass $object): string
	{
		return AnotherClass::class;
	}
}

after Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class); we'll get:

abstract class Class7
{
	function method(\Abc\AnotherClass $object): string
	{
		return AnotherClass::class;
	}
}

Expected Behavior

We must get FQN in method's and global function's body too.

abstract class Class7
{
	function method(\Abc\AnotherClass $object): string
	{
		return \Abc\AnotherClass::class;
	}
}

Possible Solution

There are two way for problem solving:

  1. We can use Nikic's PhpParser\PrettyPrinter for print traversed expression nodes from body. But prettyPrint() will changed original formatting, and printFormatPreserving() can work with whole file only... :(
  2. Collect all Name Nodes from importing method/function, and replace them in extracted body's string by position. I used this way in my fork and I'll create PR little later

"Uses" and "Extend"

  • bug report? yes
  • feature request? no
  • version: 2.6

Description

When you specify "uses", the class is not inherited correctly from the specified class, if they are partially matched and there is a difference in the registers.

Steps To Reproduce

$namespace = new PhpNamespace('app\models');
$namespace->addUse('Yii');
$class = $namespace->addClass('Demo');
$class->addExtend('yii\db\ActiveRecord');
echo "<?php\n\n".$namespace;
<?php

namespace app\models;

use Yii;

class Demo extends Yii\db\ActiveRecord // <= Wrong namespace, expected yii\db\ActiveRecord
{
}

Invalid typehints for nullable properties with union type

Version: 3.5.1

Bug Description

For now for nullable properties this library generates typehint with ?, but this conflicts with PHP8 Union Types.
Generated typehint for union types now looks like ?OneType|OtherType, which is not valid PHP syntax.
Could you fix this for PHP8 please?

Possible Solution

Check PHP version and if it's larger than 8.0 - replace ? for |nullin typehint.

Dumping an array variable does not use the Printer's overridden indentation

php version: 7.3.0
nette/php-generator version: 3.2.1

Bug Description

When adding a constant to a class as below

$map = [
    'foo' => 'bar',
    'example' => 'value',
];

$class
    ->addConstant('MAP', $map)
    ->setVisibility(ClassType::VISIBILITY_PROTECTED);

it results in:

    protected const MAP = [
	'foo' => 'bar',
	'example' => 'value',
    ];

When printing with Nette\PhpGenerator\PsrPrinter.

Expected Behavior

The constant printed with PsrPrinter's 4 spaces indentation instead of tabs.

Possible Solution

The tab is hardcoded at https://github.com/nette/php-generator/blob/master/src/PhpGenerator/Helpers.php#L74. Could we change it so that we can pass the indentation to the helper?

Printer handles null value of Property as no value incorrectly

Version: 3.4.1

Bug Description

When printing class, there is this line:

($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '

which basically means that if I set value of Property to null intentionally, this value is not printed.

Steps To Reproduce

E.g. $property->setPrivate()->setValue(null) generates private $property;
But $property->setPrivate()->setValue('test') generates private $property = 'test'; properly

Expected Behavior

It should generate private $property = null;

Possible Solution

Mark null state with some constant instead of null. So it would look something like this:

class Property
{
    public const NO_DEFAULT_VALUE = '$73aad02e-7f5a-4ef2-8cdd-0fd09e6d9863§'; // hopefully no one is going to pass this string as default value

    // ...
}

// in printer:
($property->getValue() === Property::NO_DEFAULT_VALUE && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '

Update project description.

Hi, the description of the repository still says:

🐘 Generates neat PHP code for you. Supports new PHP 7.3 features. https://nette.org

But PHP 7.4 is already supported.

Just a minor tweak, nothing important!

Your sincerely,
Mike van Diepen

Passing Array to addUse method

I have been looking for this type of library which helps me lot to generate the code

I am Working with laravel so i have created the base example model for post

$namespace = new Nette\PhpGenerator\PhpNamespace('App\Models');

$namespace->addUse('Illuminate\Database\Eloquent\Model');
$namespace->addUse('Illuminate\Database\Eloquent\SoftDeletes');

And the generated Code is here

namespace App\Models;
--
   
   use Illuminate\Database\Eloquent\Model;
   use Illuminate\Database\Eloquent\SoftDeletes;
    
   /**
   * Class Post
   *
   * @package App\Models
   */
   class Post extends Model
   {
   use SoftDeletes;

And its working fine but i was planning to add multiple addUse statment so the code becomed bigger so IT WILL BE FINE IF THE addUse method accepts the array

$namespace->addUse(
[
'Illuminate\Database\Eloquent\SoftDeletes',
'Illuminate\Database\Eloquent\Model',
]
);

PhpNamespace::unresolveName() does not preserve intentional leading backslash

Due to this commit (6e1b293) it's not possible to generate namespace with leading backslash. I know it was intended, but unfortunately it can be BC break. For example Kdyby\AOP generates class namespaces with leading backslash, but classes are in custom block namespace and if the leading ** is removed, it generates nonsense:

namespace Kdyby\Aop_CG\Container_1ad39c5e84 {

use Kdyby\Aop\Pointcut\Matcher\Criteria;
use Symfony\Component\PropertyAccess\PropertyAccess;

final class Model_PagesClass_85_Model_Pages extends Model\Pages
{
    // ...                                          ^- should be \Model\Pages

It would be great to preserve leading ** (example why). Should I prepare PR or it's completely wrong approach and should it be fixed in Kdyby?

Suggested behavior (PhpNamespace.phpt:13):

$namespace = new PhpNamespace;

Assert::same('\A', $namespace->unresolveName('\A')); //preserve leading backslash
Assert::same('A', $namespace->unresolveName('A'));
Assert::same('foo\A', $namespace->unresolveName('foo\A'));

Packagist distributes an outdated version

Took me some time to figures this out, but looks like packagist is distributing a old version of the code (at least older than 9d3d8d4). Step to reproduce:

mkdir bug && cd bug
rm -rf ~/.composer/cache
composer require nette/php-generator
# gives a few lines and notably   - Installing nette/php-generator (v2.3.4) Downloading: 100%
cat vendor/nette/php-generator/src/PhpGenerator/ClassType.php | grep addComment
# nothing... the addComment is not part of ClassType, which is indeed not the case in v2.3.4

I wonder if it is related to .gitattributes being so aggressive (well first time I see it used for excluding tests, but maybe it is a long shot).

A fast fix is to download the source or force a specific revision, in which case it is all right, we have the latest version.

Feature Request: Files that return directly

I'm looking to be able to build files that return directly.

<?php
return [
    'mysql' => [
        'host'      =>  'localhost',
        'database' =>  'database',
        'username' =>  'username',
        'password' =>  'password',
    ]
];

This is a common way to configure things in various frameworks.

You can use a simple config getting function like so:

    function config($Label)
    {
        $Config = static::$ApplicationPath . '/config/' . $Label . '.php';
        if (!file_exists($Config)) {
            throw new \Exception($Config . ' not found in '.static::class.'::config()');
        }
        return require $Config;
    }

printParameters is not taking into account the rest of the method to determine line length for wrapping

Version: 3.5.2

Bug Description

The method printParameters in the Printer class checks if the length of the arguments is greater than (new Dumper)->wrapLength. But this only checks if the list of parameters exceeds the 120 character limit but ignores the string length of the rest of the method head like the visibility, method name, function keyword and the return type

Steps To Reproduce

Example code:

$file = new PhpFile();
$namespace = $file->addNamespace('Foo');
$class = $namespace->addClass('Bar');
$method = $class->addMethod('baz')
                ->setPublic()
                ->setReturnType('string');
$method->addParameter('lorem')
       ->setType('string');
$method->addParameter('ipsum')
       ->setType('string');
$method->addParameter('dolor')
       ->setType('string');
$method->addParameter('sit')
       ->setType('string');
$method->addParameter('amet')
       ->setType('string');
$method->addParameter('foo')
       ->setType('string');
$method->addParameter('bar')
       ->setType('string');

echo (new PsrPrinter())->printFile($file);

Will print the following:

<?php

namespace Foo;

class Bar
{
    public function baz(string $lorem, string $ipsum, string $dolor, string $sit, string $amet, string $foo, string $bar): string
    {
    }
}

The function baz now has a line length of 130 characters which is too long for PSR2 and PSR12

Expected Behavior

I expect, that the rest of the method head is also taken into account when checking the line length so the following output would be generated which is PSR2 and PSR12 valid:

<?php

namespace Foo;

class Bar
{
    public function baz(
        string $lorem,
        string $ipsum,
        string $dolor,
        string $sit,
        string $amet,
        string $foo,
        string $bar
    ): string {
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.