Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,25 @@ jobs:

- name: Run JavaScript unit and browser tests
run: npm test

phpstan:
name: PHPStan
runs-on: ubuntu-22.04
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
tools: composer:v2

- name: Install Composer dependencies
run: composer update --no-interaction --prefer-dist --no-progress

- name: Run PHPStan
run: composer phpstan
10 changes: 6 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Local path: `/Volumes/SRC/JsFormValidatorBundle`
- PR #173 revived the project and was merged into `formapro/master`.
- PR #174 restored GitHub Actions CI and was merged into `formapro/master`.
- PR #175 restored selected old PR fixes and was merged into `formapro/master`.
- Replacement PRs #176, #177, and #178 are open, ready for review, and were last checked as `MERGEABLE / CLEAN` with green CI.
- Replacement PRs #176, #177, and #178 were merged into `formapro/master`.
- `1.7.0-beta1` was published from `formapro/master`.

## CI

Expand All @@ -18,16 +19,17 @@ Local path: `/Volumes/SRC/JsFormValidatorBundle`
- The PHP job runs `composer update`, `composer validate --strict`, and `composer test`.
- The JavaScript job runs on Node `22` with PHP `8.3`.
- The JavaScript job installs Cypress system dependencies, then runs `composer update`, `npm install`, and `npm test`.
- The PHPStan job runs on PHP `8.3`, installs dependencies with `composer update`, warms the Symfony test cache, and runs `composer phpstan`.
- The old `.travis.yml` file was removed.
- `README.md` was updated to use a GitHub Actions badge and test instructions instead of Travis CI references.
- `package.json` no longer advertises the old Travis CI badge in the package description.

## Local Validation

- `php /tmp/jsfv-composer.phar test` passes: `3 tests, 14 assertions`.
- `npm test` passes: Jest `184 tests`; Cypress e2e `16 tests`.
- `php /tmp/jsfv-composer.phar test` passes: `5 tests, 18 assertions`.
- `php /tmp/jsfv-composer.phar phpstan` runs PHPStan with `phpstan.neon`.
- `npm test` passes: Jest `197 tests`; Cypress e2e `16 tests`.
- The local `composer` shim is broken with `Could not open input file: /Users/ton/bin/composer`, so use `/tmp/jsfv-composer.phar` locally if needed.
- The working tree was clean after the latest validation.

## GitHub Checks Note

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ Run the PHP test suite:
composer test
```

Run PHPStan static analysis:

```bash
composer phpstan
```

Run the JavaScript unit tests and Cypress browser smoke test:

```bash
Expand All @@ -205,5 +211,5 @@ composer validate --strict
git diff --check
```

The same maintained test contour is also run by GitHub Actions on pushes and
pull requests.
The same maintained test and static-analysis checks are also run by GitHub
Actions on pushes and pull requests.
9 changes: 8 additions & 1 deletion Tests/Controller/AjaxControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ private function createRegistry(ObjectRepository $repository)
}
}

class InMemoryEntity
{
}

class InMemoryRepository implements ObjectRepository
{
public function find(mixed $id): ?object
Expand Down Expand Up @@ -134,8 +138,11 @@ public function findOneBy(array $criteria): ?object
return null;
}

/**
* @return class-string<object>
*/
public function getClassName(): string
{
return 'InMemoryEntity';
return InMemoryEntity::class;
}
}
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
},

"require-dev": {
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-symfony": "^2.0",
"phpunit/phpunit": "^10.5 || ^11.5",
"symfony/asset": "^5.4 || ^6.4 || ^7.0",
"symfony/console": "^5.4 || ^6.4 || ^7.0",
Expand Down Expand Up @@ -76,6 +78,10 @@
},

"scripts": {
"phpstan": [
"APP_ENV=test APP_DEBUG=1 php Tests/app/bin/console cache:warmup --env=test",
"phpstan analyse --memory-limit=1G"
],
"test": "phpunit"
}
}
13 changes: 13 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
includes:
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-symfony/rules.neon

parameters:
level: 5
paths:
- src
- Tests/Controller
- Tests/Unit
- Tests/app/src
symfony:
containerXmlPath: Tests/app/var/cache/test/App_KernelTestDebugContainer.xml
44 changes: 33 additions & 11 deletions src/Factory/JsFormValidatorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
use Symfony\Component\Form\FormInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Component\Validator\Mapping\GetterMetadata;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
Expand Down Expand Up @@ -87,7 +87,7 @@ public function __construct(
*
* @param string $className
*
* @return ClassMetadata
* @return MetadataInterface
* @codeCoverageIgnore
*/
protected function getMetadataFor($className)
Expand Down Expand Up @@ -170,7 +170,7 @@ public function getQueue()
*
* @param FormInterface $form
*
* @return array
* @return void
*/
public function addToQueue(FormInterface $form)
{
Expand Down Expand Up @@ -316,15 +316,16 @@ protected function getValidationData(FormInterface $form)
// If parent has metadata
$parent = $form->getParent();
if ($parent && null !== $parent->getConfig()->getDataClass()) {
$classMetadata = $metadata = $this->getMetadataFor($parent->getConfig()->getDataClass());
if ($classMetadata->hasPropertyMetadata($form->getName())) {
$classMetadata = $this->getMetadataFor($parent->getConfig()->getDataClass());
if ($classMetadata instanceof ClassMetadataInterface && $classMetadata->hasPropertyMetadata($form->getName())) {
$metadata = $classMetadata->getPropertyMetadata($form->getName());
/** @var PropertyMetadata $item */
foreach ($metadata as $item) {
$constraints = $item instanceof GetterMetadata ? array() : $item->getConstraints();
$getters = $item instanceof GetterMetadata ? array($item) : array();
$this->composeValidationData(
$parentData,
$item->getConstraints(),
$getters = !empty($item->getters) ? (array)$item->getters : array()
$constraints,
$getters
);
}
}
Expand All @@ -335,7 +336,7 @@ protected function getValidationData(FormInterface $form)
$this->composeValidationData(
$ownData,
$metadata->getConstraints(),
$getters = !empty($metadata->getters) ? (array)$metadata->getters : array()
$this->getGetterMetadata($metadata)
);
}
// If has constraints in a form element
Expand Down Expand Up @@ -364,6 +365,27 @@ protected function getValidationData(FormInterface $form)
return $result;
}

/**
* @return GetterMetadata[]
*/
protected function getGetterMetadata(MetadataInterface $metadata)
{
if (!$metadata instanceof ClassMetadataInterface) {
return array();
}

$getters = array();
foreach ($metadata->getConstrainedProperties() as $property) {
foreach ($metadata->getPropertyMetadata($property) as $item) {
if ($item instanceof GetterMetadata) {
$getters[] = $item;
}
}
}

return $getters;
}

protected function mergeDataRecursive(array $array1, array $array2)
{
foreach ($array2 as $key => $value) {
Expand Down Expand Up @@ -515,7 +537,7 @@ protected function getTransformerParam(DataTransformerInterface $transformer, $p
$reflection = new \ReflectionProperty($transformer, $paramName);
$reflection->setAccessible(true);

if (method_exists($reflection, 'isInitialized') && !$reflection->isInitialized($transformer)) {
if (!$reflection->isInitialized($transformer)) {
return null;
}

Expand Down
8 changes: 4 additions & 4 deletions src/Form/Constraint/UniqueEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ class UniqueEntity extends BaseUniqueEntity
*/
public function __construct(BaseUniqueEntity $base, $entityName, $entity = null)
{
foreach (get_object_vars($base) as $prop => $value) {
$this->{$prop} = $value;
}

$this->entityName = $entityName;
if (is_object($entity)) {
$this->entity = $entity;
if (method_exists($entity, 'getId')) {
$this->entityId = $entity->getId();
}
}

foreach ($base as $prop => $value) {
$this->{$prop} = $value;
}
}
}
7 changes: 4 additions & 3 deletions src/Model/JsModelAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public static function phpValueToJs($value)
// For an object or associative array
elseif (is_object($value) || (is_array($value) && array_values($value) !== $value)) {
$jsObject = array();
foreach ($value as $paramName => $paramValue) {
$properties = is_object($value) ? get_object_vars($value) : $value;
foreach ($properties as $paramName => $paramValue) {
$paramName = addcslashes($paramName, '\'\\');
$jsObject[] = "'$paramName':" . self::phpValueToJs($paramValue);
}
Expand Down Expand Up @@ -77,7 +78,7 @@ public static function phpValueToJs($value)
}
// For numbers
elseif (is_numeric($value)) {
return $value;
return (string) $value;
}
// For null
elseif (is_null($value)) {
Expand All @@ -95,7 +96,7 @@ public static function phpValueToJs($value)
public function toArray()
{
$result = array();
foreach ($this as $key => $value) {
foreach (get_object_vars($this) as $key => $value) {
$result[$key] = $value;
}

Expand Down
Loading