Skip to content

Commit

Permalink
move reoccuring functions to fpp namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
prolic committed Jun 12, 2020
1 parent fcecb94 commit aa07276
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 378 deletions.
14 changes: 11 additions & 3 deletions demo/example.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ namespace Foo\Bar {
marker Event;
marker Command;

data Person = NonPerson { DateTimeImmutable $registered, ?UserId $userId, Name[] $names, ?int $age = 18, string[] $emails = [], HumanId[] $humanIds,
Address[] $addresses
} | GoodPerson { UserId $userId, ?int $age = 18 };
data Person =
NonPerson {
DateTimeImmutable $registered,
?UserId $userId,
Name[] $names,
?int $age = 18,
string[] $emails = [],
HumanId[] $humanIds,
Address[] $addresses
}
| GoodPerson { UserId $userId, ?int $age = 18 };

data Address = { string $street, int $no };

Expand Down
206 changes: 206 additions & 0 deletions src/Functions/fpp.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,209 @@ function renameDuplicateArgumentNames(array $names, array $arguments): array

return $result;
}

const generateValidationFor = 'Fpp\generateValidationFor';

function generateValidationFor(
Argument $a,
string $paramName,
?string $resolvedType,
array $definitions,
Configuration $config
): string {
if ($a->isList()) {
$code = "if (! isset(\$data['{$a->name()}']) || ! \is_array(\${$paramName}['{$a->name()}'])) {\n";
$code .= " throw new \InvalidArgumentException('Error on \"{$a->name()}\": array expected');\n";
$code .= "}\n\n";

return $code;
}

if ($a->nullable()) {
$code = "if (isset(\${$paramName}['{$a->name()}']) && ! {%validation%}) {\n";
$code .= " throw new \InvalidArgumentException('{%validationErrorMessage%}');\n";
$code .= "}\n\n";
} else {
$code = "if (! isset(\${$paramName}['{$a->name()}']) || ! {%validation%}) {\n";
$code .= " throw new \InvalidArgumentException('{%validationErrorMessage%}');\n";
$code .= "}\n\n";
}

switch ($a->type()) {
case null:
return '';
case 'int':
$validation = "\is_int(\${$paramName}['{$a->name()}'])";
$validationErrorMessage = "Error on \"{$a->name()}\", int expected";
break;
case 'float':
$validation = "\is_float(\${$paramName}['{$a->name()}'])";
$validationErrorMessage = "Error on \"{$a->name()}\", float expected";
break;
case 'bool':
$validation = "\is_bool(\${$paramName}['{$a->name()}'])";
$validationErrorMessage = "Error on \"{$a->name()}\", bool expected";
break;
case 'string':
$validation = "\is_string(\${$paramName}['{$a->name()}'])";
$validationErrorMessage = "Error on \"{$a->name()}\", string expected";
break;
case 'array':
$validation = "\is_array(\${$paramName}['{$a->name()}'])";
$validationErrorMessage = "Error on \"{$a->name()}\", array expected";
break;
default:
$definition = $definitions[$resolvedType] ?? null;

if (null === $definition) {
/** @var TypeConfiguration|null $typeConfig */
$typeConfig = $config->types()[$resolvedType] ?? null;

if (null === $typeConfig) {
return '';
}

$validator = $typeConfig->validator();
$validatorErrorMessage = $typeConfig->validationErrorMessage();
} else {
$type = $definition->type();

$validator = $config->validatorFor($type);
$validatorErrorMessage = $config->validationErrorMessageFor($type);
}

$validation = $validator("{$paramName}['{$a->name()}']");
$validationErrorMessage = $validatorErrorMessage("\$data[\'{$a->name()}\']");

break;
}

return \str_replace(
[
'{%validation%}',
'{%validationErrorMessage%}',
],
[
$validation,
$validationErrorMessage,
],
$code
);
}

const generateFromPhpValueFor = 'Fpp\generateFromPhpValueFor';

function generateFromPhpValueFor(
Argument $a,
string $paramName,
int $intentLevel,
?string $resolvedType,
array $definitions,
Configuration $config
): string {
$intent = \str_repeat(' ', $intentLevel * 4);

switch ($a->type()) {
case null:
case 'int':
case 'float':
case 'bool':
case 'string':
case 'array':
// yes all above are treated the same
if ($a->nullable()) {
return "{$intent}{$paramName}['{$a->name()}'] ?? null";
}

return "{$intent}{$paramName}['{$a->name()}']";
default:
$definition = $definitions[$resolvedType] ?? null;

if (null === $definition) {
/** @var TypeConfiguration|null $typeConfig */
$typeConfig = $config->types()[$resolvedType] ?? null;

if (null === $typeConfig) {
return "{$intent}{$paramName}['{$a->name()}']";
}

if ($a->isList()) {
return <<<CODE
{$intent}\array_map(fn (\$e) => {$typeConfig->fromPhpValue()($a->type(), 'e')}, {$paramName}['{$a->name()}'])
CODE;
}

return $intent . $typeConfig->fromPhpValue()($a->type(), "this->payload['{$a->name()}']");
}

$builder = $config->fromPhpValueFor($definition->type());

if ($a->isList()) {
$callback = "fn(\$e) => {$builder($definition->type(), '$e')}";

return "{$intent}\array_map($callback, {$paramName}['{$a->name()}'])";
}

if ($a->nullable()) {
return "{$intent}isset({$paramName}['{$a->name()}']) ? " . $builder($definition->type(), "{$paramName}['{$a->name()}']") . ' : null';
}

return $intent . $builder($definition->type(), "{$paramName}['{$a->name()}']") . '';
}
}

function generateToArrayBodyFor(Argument $a, $prefix, ?string $resolvedType, array $definitions, Configuration $config): string
{
switch ($a->type()) {
case null:
case 'int':
case 'float':
case 'bool':
case 'string':
case 'array':
// yes all above are treated the same
return " '{$a->name()}' => {$prefix}{$a->name()},\n";
default:
$definition = $definitions[$resolvedType] ?? null;

if (null === $definition) {
/** @var TypeConfiguration|null $typeConfiguration */
$typeConfiguration = $config->types()[$resolvedType] ?? null;

if (null === $typeConfiguration) {
return " '{$a->name()}' => {$prefix}{$a->name()},\n";
}

if ($a->isList()) {
$callback = "fn({$a->type()} \$e) => {$typeConfiguration->toPhpValue()('$e')}";

return <<<CODE
'{$a->name()}' => \array_map($callback, {$prefix}{$a->name()}),
CODE;
}

if ($a->nullable()) {
return " '{$a->name()}' => {$prefix}{$a->name()} !=== null ? "
. ($typeConfiguration->toPhpValue()('$this->' . $a->name())) . " : null,\n";
}

return " '{$a->name()}' => " . ($typeConfiguration->toPhpValue()('$this->' . $a->name())) . ",\n";
}

$builder = $config->toPhpValueFor($definition->type());

if ($a->isList()) {
$callback = "fn({$a->type()} \$e) => {$builder($definition->type(), '$e')}";

return " '{$a->name()}' => \array_map($callback, {$prefix}{$a->name()}),\n";
}

if ($a->nullable()) {
return " '{$a->name()}' => {$prefix}{$a->name()} !== null ? {$prefix}"
. ($builder)($definition->type(), $a->name()) . " : null,\n";
}

return " '{$a->name()}' => {$prefix}" . ($builder)($definition->type(), $a->name()) . ",\n";
}
}
4 changes: 2 additions & 2 deletions src/Type/Bool_.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ function build(Definition $definition, array $definitions, Configuration $config
$method->setBody('return $this->value;');

$method = $class->addMethod('equals')->setReturnType(Type::BOOL);
$method->addParameter('other')->setType(Type::SELF);
$method->setBody('return $this->value === $other->value;');
$method->addParameter('other')->setType(Type::SELF)->setNullable();
$method->setBody('return null !== $other && $this->value === $other->value;');

return [$fqcn => $file];
}
Expand Down
77 changes: 23 additions & 54 deletions src/Type/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use Fpp\Configuration;
use function Fpp\constructorSeparator;
use Fpp\Definition;
use function Fpp\generateFromPhpValueFor;
use function Fpp\generateValidationFor;
use function Fpp\many1;
use function Fpp\not;
use function Fpp\parseArguments;
Expand Down Expand Up @@ -138,15 +140,21 @@ function build(Definition $definition, array $definitions, Configuration $config
->setDefaultValue([]);
$constructor->setBody(<<<CODE
\$this->commandId = \$commandId ?? {$type->commandIdType()}::generate();
\$this->payload = \$payload;
\$this->metadata = \$metadata;
\$this->setPayload(\$payload);
CODE
);

$class->addMethod('commandType')
->setAbstract()
->setReturnType(Type::STRING);

$class->addMethod('setPayload')
->setProtected()
->setReturnType(Type::VOID)
->setBody('$this->payload = $payload;')
->addParameter('payload')->setType(Type::ARRAY);

$class->addMethod('commandId')
->setBody('return $this->commandId;')
->setReturnType($type->commandIdType());
Expand Down Expand Up @@ -395,15 +403,24 @@ function buildSubType(
->setReturnType(Type::STRING)
->setBody('return $this->commandType;');

$setPayloadMethod = $class->addMethod('setPayload')
->setProtected()
->setReturnType(Type::VOID);

$setPayloadMethod->addParameter('payload')->setType(Type::ARRAY);
$setPayloadMethodBody = '';

\array_map(
function (Argument $a) use (
$class,
$definition,
$definitions,
$config
$config,
&$setPayloadMethodBody
) {
$resolvedType = resolveType($a->type(), $definition);
$fromPhpValue = calculateFromPhpValueFor($a, $resolvedType, $definitions, $config);
$fromPhpValue = generateFromPhpValueFor($a, '$this->payload', 0, $resolvedType, $definitions, $config);
$setPayloadMethodBody .= generateValidationFor($a, 'payload', $resolvedType, $definitions, $config);

$psalmAnnotation = '';

Expand All @@ -427,58 +444,10 @@ function (Argument $a) use (
$constructor->arguments()
);

return $file;
}

function calculateFromPhpValueFor(Argument $a, ?string $resolvedType, array $definitions, Configuration $config): string
{
switch ($a->type()) {
case null:
case 'int':
case 'float':
case 'bool':
case 'string':
case 'array':
// yes all above are treated the same
if ($a->nullable()) {
return "\$this->payload['{$a->name()}'] ?? null";
}

return "\$this->payload['{$a->name()}']";
default:
$definition = $definitions[$resolvedType] ?? null;

if (null === $definition) {
/** @var TypeConfiguration|null $typeConfig */
$typeConfig = $config->types()[$resolvedType] ?? null;

if (null === $typeConfig) {
return "\$this->payload['{$a->name()}']";
}

if ($a->isList()) {
return <<<CODE
\array_map(fn (\$e) => {$typeConfig->fromPhpValue()($a->type(), 'e')}, \$this->payload['{$a->name()}'])
CODE;
}

return $typeConfig->fromPhpValue()($a->type(), "this->payload['{$a->name()}']");
}

$builder = $config->fromPhpValueFor($definition->type());

if ($a->isList()) {
$callback = "fn(\$e) => {$builder($definition->type(), '$e')}";

return "\array_map($callback, \$this->payload['{$a->name()}'])";
}
$setPayloadMethodBody .= 'parent::setPayload($payload);';
$setPayloadMethod->setBody($setPayloadMethodBody);

if ($a->nullable()) {
return "isset(\$this->payload['{$a->name()}']) ? " . $builder($definition->type(), "\$this->payload['{$a->name()}']") . ' : null';
}

return $builder($definition->type(), "\$this->payload['{$a->name()}']") . '';
}
return $file;
}

function commandType(Constructor $constructor, string $namespace): string
Expand Down
Loading

0 comments on commit aa07276

Please sign in to comment.