├── .env ├── .gitignore ├── LICENSE ├── README.md ├── _tuts ├── README.md ├── acl-add-isvalidownervalidator.diff ├── acl-add-notblank-validation.diff ├── acl-allow-admin-to-change.diff ├── acl-fill-in-some-validator-logic.diff ├── acl-finishing-validator.diff ├── acl-group-to-only-allow-cl-owner-on-post.diff ├── acl-test-with-bad-cl-owner.diff ├── auto-owner-add-tag.diff ├── auto-owner-create-listener.diff ├── auto-owner-enhance-test.diff ├── auto-owner-finish-logic.diff ├── autogroups-add-default-groups.diff ├── autogroups-correct-cheeses.diff ├── autogroups-decorates-priority.diff ├── autogroups-empty-resource-factory.diff ├── autogroups-remove-custom-groups.diff ├── autogroups-with-resource-factory.diff ├── customitem-add-item-provider-interface.diff ├── customitem-dump-id.diff ├── customitem-finish-getitem.diff ├── customitem-reactivate-get-item.diff ├── customitem-refactor-provider.diff ├── customput-add-empty-data-persister.diff ├── customput-add-put-operation.diff ├── customput-fill-in-supports.diff ├── customput-log-in-persist.diff ├── customresource-add-constructor.diff ├── customresource-add-getter-for-identifier.diff ├── customresource-add-item-with-404.diff ├── customresource-add-normalization-groups.diff ├── customresource-create-dailystats.diff ├── customresource-customize-namer-instead.diff ├── customresource-customize-shortname.diff ├── customresource-description-on-prop.diff ├── customresource-empty-data-provider.diff ├── customresource-only-get-collection-op.diff ├── customresource-return-dummy-dailystats.diff ├── customresource-set-identifier-to-date.diff ├── delete-me-1.diff ├── delete-me-10.diff ├── delete-me-3.diff ├── delete-me-5.diff ├── delete-me-6.diff ├── delete-me-7.diff ├── delete-me-8.diff ├── delete-me-9.diff ├── dtofilter-add-context-aware-to-provider.diff ├── dtofilter-add-filter-class.diff ├── dtofilter-add-from-support-to-paginator.diff ├── dtofilter-dd-in-apply.diff ├── dtofilter-dump-from-query-param.diff ├── dtofilter-fill-in-getdescription.diff ├── dtofilter-pass-in-hardcoded-from.diff ├── dtofilter-return-400-error.diff ├── dtofilter-set-date-on-context.diff ├── dtofilter-set-from-in-data-provider.diff ├── dtoinput-add-class.diff ├── dtoinput-add-phpdoc.diff ├── dtoinput-add-var-for-other-props.diff ├── dtoinput-add-var-user.diff ├── dtoinput-complete-transform.diff ├── dtoinput-create-class-with-props.diff ├── dtoinput-dump-arguments.diff ├── dtoinput-empty-transformer.diff ├── dtoinput-fill-in-supports.diff ├── dtoinput-install-debug.diff ├── dtoinput-make-it-work-for-updates.diff ├── dtonormalizeissue-add-readablelink.diff ├── dtonormalizeissue-add-user-read-groups.diff ├── dtonormalizeissue-re-add-readablelink.diff ├── dtonormalizeissue-remove-extra-groups.diff ├── dtonormalizeissue-remove-readablelink.diff ├── dtooption-add-throwoninvalid.diff ├── dtooption-inject-logger.diff ├── dtooption-pass-throwoninvalid.diff ├── dtoorg-add-createfrominputdto.diff ├── dtoorg-add-input-createfromentity.diff ├── dtoorg-add-input-createorupdateentity.diff ├── dtooutput-add-createdat-field.diff ├── dtooutput-add-empty-transformer.diff ├── dtooutput-add-more-fields.diff ├── dtooutput-add-output.diff ├── dtooutput-add-string-field-type.diff ├── dtooutput-add-supports.diff ├── dtooutput-basic-transform.diff ├── dtooutput-copy-exposed-getter.diff ├── dtooutput-copy-other-method.diff ├── dtooutput-create-basic-cheeselistingoutput.diff ├── dtooutput-dd-in-supports.diff ├── dtooutput-give-field-groups.diff ├── dtooutput-move-to-var.diff ├── dtoownerprop-add-owner.diff ├── dtoownerprop-fix-test.diff ├── dtoownerprop-fix-usernormalizer.diff ├── dtoupdate-add-basic-dto-object-to-context.diff ├── dtoupdate-empty-denormalizer.diff ├── dtoupdate-fill-in-supports.diff ├── dtoupdate-inject-objectnormalizer.diff ├── dtoupdate-paste-in-private-function.diff ├── dtoupdate-remove-dump.diff ├── dtoupdate-use-private-method.diff ├── dynamserialization-add-admin-groups.diff ├── dynamserialization-add-phonenumber.diff ├── dynamserialization-add-test-for-phonenumber-admin-only.diff ├── dynamserialization-change-to-admin-write.diff ├── dynamserialization-chnage-to-admin-read.diff ├── dynamserialization-copy-in-context-builder.diff ├── dynamserialization-groups-for-phonenumber.diff ├── dynamserialization-input-group-for-user-roles.diff ├── dynamserialization-migration.diff ├── dynamserialization-protect-roles.diff ├── dynamserialization-test-for-roles-update.diff ├── embeddednormalization-advertise-return-type.diff ├── embeddednormalization-help-my-editor.diff ├── embeddednormalization-output-2x-cl-fields-on-user.diff ├── embeddednormalization-redo-listing-docs.diff ├── embeddednormalization-return-2-stats.diff ├── embeddednormalization-return-some-listings.diff ├── embeddednormalization-undo-everything.diff ├── embeddednormalization-with-readablelink.diff ├── encoding-add-plainpassword-field.diff ├── encoding-bootstrap-data-persister.diff ├── encoding-customize-getter-setter.diff ├── encoding-encode-password.diff ├── encoding-erasecredentials.diff ├── encoding-remove-password-from-group.diff ├── encoding-rename-field-to-password.diff ├── encoding-test-for-user-registration.diff ├── encoding-use-entity-manager.diff ├── entityfilter-add-cheesesearchfilter.diff ├── entityfilter-add-documentation.diff ├── entityfilter-add-like-query.diff ├── entityfilter-add-openapi-description.diff ├── entityfilter-add-properties-option.diff ├── entityfilter-dump-in-getdescription.diff ├── entityfilter-dump-property-and-value.diff ├── entityfilter-remove-or.diff ├── entityfilter-use-query-name-generator.diff ├── entityfilteroption-add-uselike-argument.diff ├── entityfilteroption-add-uselike.diff ├── entityfilteroption-override-constructor.diff ├── entityfilteroption-simplify-constructor.diff ├── extension-add-admin-exception.diff ├── extension-add-extension-item-interface.diff ├── extension-add-ispublished-where.diff ├── extension-assert-404-on-test.diff ├── extension-change-test-for-published.diff ├── extension-empty-cheeselistingispublishedextension.diff ├── extension-test-for-single-item.diff ├── extension-test-get-cl-collection.diff ├── filter-add-2nd-field-to-searchfilter.diff ├── filter-add-booleanfilter.diff ├── filter-add-getshortdescription.diff ├── filter-add-missing-use.diff ├── filter-add-propertyfilter.diff ├── filter-add-rangefilter.diff ├── filter-add-searchfilter.diff ├── first-resource-install-maker.diff ├── first-resource-install-migrations.diff ├── first-resource-make-entity.diff ├── first-resource-make-migration.diff ├── formats-add-csv-to-resource.diff ├── formats-add-hal.diff ├── formats-paste-formats-config.diff ├── groups-add-denormalization-group.diff ├── groups-add-normalization-group.diff ├── groups-add-phpdoc-above-method.diff ├── groups-add-read-group-to-some-props.diff ├── groups-add-read-to-getcreatedatago.diff ├── groups-add-settextdescription-to-write-group.diff ├── groups-add-write-group-to-props.diff ├── groups-default-ispublished.diff ├── groups-name-swagger-models.diff ├── groups-re-add-setdescription.diff ├── hydra-add-var-description.diff ├── installation-configure-db.diff ├── installation-require-api.diff ├── installation-upgrade-doctrine-annotations.diff ├── ismelistener-add-event-subscriber.diff ├── ismelistener-add-logic.diff ├── ismelistener-comment-out-other-code.diff ├── ismelistener-configure-lsitener.diff ├── ismelistener-dd-data-attributes.diff ├── ismelistener-default-isme-to-false.diff ├── ismelistener-remove-dd.diff ├── ismvplistener-add-field.diff ├── ismvplistener-add-listener.diff ├── ismvplistener-rename-method.diff ├── ismvplistener-update-test.diff ├── jsonlogin-emitting-vue-authenticated.diff ├── jsonlogin-error-on-bad-content-type-header.diff ├── jsonlogin-fix-content-type.diff ├── jsonlogin-handle-auth-error.diff ├── jsonlogin-json-login-simple-controller.diff ├── jsonlogin-login-ajax-request.diff ├── jsonlogin-return-location-header-on-success.diff ├── jsonlogin-show-bad-content-type-header.diff ├── logout-add-logout-handler.diff ├── normalizer-add-isme-field.diff ├── normalizer-check-true-security.diff ├── normalizer-fill-in-dummy-logic.diff ├── normalizer-fix-missing-json-ld-fields.diff ├── normalizer-fix-phonenumber-test.diff ├── normalizer-fix-recursion.diff ├── normalizer-make-usernormalizer.diff ├── normalizer-return-false-cacheable.diff ├── operations-configure-shortname.diff ├── operations-configure-to-use-all.diff ├── operations-remove-delete-operation.diff ├── operations-remove-extra-config.diff ├── operations-show-expanded-operation.diff ├── pagination-add-pagination-items-per-page.diff ├── pagination-create-dailystatspaginator.diff ├── pagination-fill-in-dummy-values.diff ├── pagination-inject-pagination-object.diff ├── pagination-limit-to-7-per-page.diff ├── pagination-make-it-iterable.diff ├── pagination-move-stats-loading-logic.diff ├── pagination-pass-current-max-per-page.diff ├── pagination-pass-in-arguments.diff ├── pagination-return-paginator-object.diff ├── pagination-update-count.diff ├── pagination-use-limit-offset-in-results.diff ├── persisterdecoration-add-context-interface.diff ├── persisterdecoration-add-service-config.diff ├── persisterdecoration-comment-out-persist.diff ├── persisterdecoration-dd-context.diff ├── persisterdecoration-inject-decorated.diff ├── persisterdecoration-log-on-user-create.diff ├── persisterdecoration-re-add.diff ├── persisterdecoration-remove-dd.diff ├── pre-ep2-add-test-stuff-to-tutorial.diff ├── pre-ep2-create-entire-vue-frontend.diff ├── pre-ep2-install-encore.diff ├── pre-ep2-tweaking-test-dir.diff ├── pre-ep2-updating-recipe.diff ├── pre-ep2-upgrade-dependencies.diff ├── pre-ep2-using-auto-encoder.diff ├── pre-ep3-add-cheesenotification-entity.diff ├── pre-ep3-add-docker-compose-yaml.diff ├── pre-ep3-add-fixtures.diff ├── pre-ep3-add-isme-tests.diff ├── pre-ep3-adding-tutorial-fake-stats.diff ├── pre-ep3-annotations-recipe.diff ├── pre-ep3-api-platform-recipe.diff ├── pre-ep3-api-platform-security-changes.diff ├── pre-ep3-auto-fill-login.diff ├── pre-ep3-bumping-php-version-and-re-org-deps.diff ├── pre-ep3-commit-webpack-assets.diff ├── pre-ep3-disabling-deprecations-in-tests.diff ├── pre-ep3-doctrinebundle-recipe.diff ├── pre-ep3-emptying-tutorial.diff ├── pre-ep3-encore-recipe.diff ├── pre-ep3-factory-for-cheesenotification.diff ├── pre-ep3-fill-in-factories.diff ├── pre-ep3-fix-email-in-tests.diff ├── pre-ep3-fix-item-query-extension.diff ├── pre-ep3-fixing-small-deprecation.diff ├── pre-ep3-flex-recipe.diff ├── pre-ep3-foundry-in-fixtures.diff ├── pre-ep3-frameworkbundle-recipe.diff ├── pre-ep3-generate-factories.diff ├── pre-ep3-improve-cheeselisting-test.diff ├── pre-ep3-install-doctrine-fixtures.diff ├── pre-ep3-install-foundry.diff ├── pre-ep3-make-test-encoding-faster.diff ├── pre-ep3-modernizing-vue-app.diff ├── pre-ep3-monolog-recipe.diff ├── pre-ep3-nelmio-cors-recipe.diff ├── pre-ep3-phpunit-bridge-recipe.diff ├── pre-ep3-remove-using-owner-read.diff ├── pre-ep3-removing-auto-group-factory.diff ├── pre-ep3-replace-alice-with-foundry.diff ├── pre-ep3-security-recipe.diff ├── pre-ep3-symfony-console-recipe.diff ├── pre-ep3-symfony-routing-recipe.diff ├── pre-ep3-translation-recipe.diff ├── pre-ep3-twig-recipe.diff ├── pre-ep3-unpacking-apip-pack.diff ├── pre-ep3-unpacking-profiler-test-packs.diff ├── pre-ep3-updating-deps-to-sf5.diff ├── pre-ep3-updating-repositories.diff ├── pre-ep3-updating-to-migrations-3.diff ├── pre-ep3-upgrade-yarn-deps.diff ├── pre-ep3-use-foundry-in-tests.diff ├── pre-ep3-using-core-test-classes.diff ├── pre-ep3-validator-recipe.diff ├── profiler-install-debug.diff ├── relation-add-cheeselisting-owner.diff ├── relation-add-failing-test.diff ├── relation-add-filtered-getter.diff ├── relation-add-new-cl-to-new-user.diff ├── relation-add-orphanremoval.diff ├── relation-add-valid-annotation.diff ├── relation-allow-embedded-write.diff ├── relation-embed-cheeselisting-owner.diff ├── relation-embed-user-cheeselistings.diff ├── relation-expose-cheeselisting-owner.diff ├── relation-expose-user-cheeselistings.diff ├── relation-fix-update-published-test.diff ├── relation-make-migration.diff ├── relation-only-embed-on-get-item.diff ├── relation-propertyfilter-on-relation.diff ├── relation-set-items-on-collection.diff ├── relation-valid-on-cl-owner.diff ├── relationfilter-filter-on-cheeselisting-owner.diff ├── relationfilter-filter-on-owner-username.diff ├── relationfilter-use-multiple-lines.diff ├── security-access-control-for-user.diff ├── security-access-control-message.diff ├── security-only-allow-owners-to-put.diff ├── security-post-requires-role-user.diff ├── security-protect-item-operations.diff ├── security-remove-debug-code.diff ├── security-set-collectionoperations.diff ├── security-show-previous-object.diff ├── security-test-for-reassigning-owner.diff ├── security-use-top-level-access-control.diff ├── serialization-add-settextdescription.diff ├── serialization-getcreatedatago.diff ├── serialization-install-carbon.diff ├── serialization-remove-setcreatedat.diff ├── serialization-remove-setdescription.diff ├── serializetricks-change-arg-name.diff ├── serializetricks-change-back.diff ├── serializetricks-constructor.diff ├── serializetricks-make-optional.diff ├── serializetricks-serializedname.diff ├── statechangeaction-add-phpdoc.diff ├── statechangeaction-call-decorated.diff ├── statechangeaction-check-if-ispublished.diff ├── statechangeaction-check-originaldata.diff ├── statechangeaction-create-empty-persister.diff ├── statechangeaction-get-original-data.diff ├── statechangeaction-inject-decorated.diff ├── statechangeaction-insert-notification.diff ├── statechangetest-bootstrap-test.diff ├── statechangetest-expose-ispublished-for-write.diff ├── statechangetest-test-for-notification.diff ├── statevalidation-add-constraint.diff ├── statevalidation-add-long-text.diff ├── statevalidation-add-test.diff ├── statevalidation-add-unpublish-logic.diff ├── statevalidation-allow-admins-to-publish.diff ├── statevalidation-allow-on-class.diff ├── statevalidation-comment-out-access-denied.diff ├── statevalidation-do-not-allow-short-description.diff ├── statevalidation-do-nothing-if-no-change.diff ├── statevalidation-dump-in-validator.diff ├── statevalidation-dump-original-data.diff ├── statevalidation-generate-validator.diff ├── statevalidation-show-accessdeniedexception.diff ├── steps.json ├── subresource-remove-it.diff ├── subresource-user-cheeselistings.diff ├── swagger-disable-docs.diff ├── swagger-disable-entrypoint.diff ├── swagger-disable-in-prod.diff ├── swagger-undo-that.diff ├── test-add-content-type.diff ├── test-add-createuserandlogin.diff ├── test-add-customapitestcase-base-class.diff ├── test-add-put-test.diff ├── test-add-test-service.diff ├── test-assert-auth-success-with-400.diff ├── test-copy-in-test-directory.diff ├── test-create-user-log-in.diff ├── test-encode-password.diff ├── test-first-simple-test.diff ├── test-install-alicebundle.diff ├── test-install-http-client.diff ├── test-install-monolog-bundle.diff ├── test-install-simple-phpunit.diff ├── test-override-database-url.diff ├── test-put-403-on-wrong-user.diff ├── test-remove-extra-content-type.diff ├── test-send-valid-json.diff ├── test-test-for-401.diff ├── test-use-phpunit-8.diff ├── test-use-reloaddatabasetrait.diff ├── user-add-serialization-groups.diff ├── user-add-validation.diff ├── user-fix-getusername.diff ├── user-make-apiresource.diff ├── user-make-entity-add-username.diff ├── user-make-migration.diff ├── user-make-user.diff ├── user-make-username-unique.diff ├── user-pass-initial-user-from-twig.diff ├── userisme-add-isme-field.diff ├── userisme-add-item-provider-interface.diff ├── userisme-add-phpdoc.diff ├── userisme-add-response-status-code-check.diff ├── userisme-decorate-collection-provider.diff ├── userisme-decorate-core-provider.diff ├── userisme-dump-users.diff ├── userisme-empty-data-provider.diff ├── userisme-exception-if-not-initialized.diff ├── userisme-fill-in-supports.diff ├── userisme-inject-specific-provider.diff ├── userisme-populate-correctly.diff ├── userisme-populate-isme-in-data-persister.diff ├── userisme-populate-isme-in-item.diff ├── userisme-populate-the-field.diff ├── userisme-remove-in-normalizer.diff ├── userisme-return-all-users.diff ├── uuid-add-test-to-set-on-create.diff ├── uuid-add-uuid-field.diff ├── uuid-change-identifier.diff ├── uuid-check-for-uuid-in-test.diff ├── uuid-expose-uuid-in-constructor.diff ├── uuid-fetch-user-in-test.diff ├── uuid-initialize-in-the-constructor.diff ├── uuid-install-ramsey-uuid-doctrine.diff ├── uuid-make-migration-safe.diff ├── uuid-make-not-null.diff ├── uuid-re-add-json-format.diff ├── uuid-remove-header.diff ├── uuid-remove-json-format.diff ├── uuid-rename-to-id.diff ├── uuid-send-json-ld-content-type.diff ├── uuid-use-uuid-in-all-tests.diff ├── validation-add-basic-constraints.diff ├── validation-cast-to-avoid-errors.diff ├── validation-inject-validate.diff ├── validation-move-constraints-to-input.diff ├── validation-undo-casting.diff ├── validationgroups-add-create-group.diff ├── validationgroups-add-test-for-update.diff ├── validationgroups-plainpassword-notblank.diff ├── voter-add-edit-case.diff ├── voter-check-for-role-admin.diff ├── voter-exception-on-edge-case.diff └── voter-make-voter.diff ├── bin └── console ├── composer.json ├── composer.lock ├── config ├── bootstrap.php ├── bundles.php ├── packages │ ├── cache.yaml │ ├── dev │ │ └── routing.yaml │ ├── framework.yaml │ ├── routing.yaml │ └── test │ │ ├── framework.yaml │ │ └── routing.yaml ├── routes.yaml └── services.yaml ├── public └── index.php ├── sfcasts ├── ep1-basics │ ├── api-resource.md │ ├── collections-create.md │ ├── collections-remove-item.md │ ├── collections-write.md │ ├── embedded-write.md │ ├── embedded.md │ ├── es │ │ ├── api-resource.md │ │ ├── api-resource.vtt │ │ ├── collections-create.md │ │ ├── collections-create.vtt │ │ ├── collections-remove-item.md │ │ ├── collections-remove-item.vtt │ │ ├── collections-write.md │ │ ├── collections-write.vtt │ │ ├── embedded-write.md │ │ ├── embedded-write.vtt │ │ ├── embedded.md │ │ ├── embedded.vtt │ │ ├── filters.md │ │ ├── filters.vtt │ │ ├── formats.md │ │ ├── formats.vtt │ │ ├── hydra.md │ │ ├── hydra.vtt │ │ ├── install.md │ │ ├── install.vtt │ │ ├── json-ld.md │ │ ├── json-ld.vtt │ │ ├── open-api-spec.md │ │ ├── open-api-spec.vtt │ │ ├── operations.md │ │ ├── operations.vtt │ │ ├── pagination.md │ │ ├── pagination.vtt │ │ ├── profiler.md │ │ ├── profiler.vtt │ │ ├── property-filter.md │ │ ├── property-filter.vtt │ │ ├── relation-filtering.md │ │ ├── relation-filtering.vtt │ │ ├── relations-iri.md │ │ ├── relations-iri.vtt │ │ ├── relations.md │ │ ├── relations.vtt │ │ ├── serialization-groups.md │ │ ├── serialization-groups.vtt │ │ ├── serialization-tricks.md │ │ ├── serialization-tricks.vtt │ │ ├── serializer.md │ │ ├── serializer.vtt │ │ ├── subresources.md │ │ ├── subresources.vtt │ │ ├── swagger.md │ │ ├── swagger.vtt │ │ ├── user-entity.md │ │ ├── user-entity.vtt │ │ ├── user-resource.md │ │ ├── user-resource.vtt │ │ ├── validation.md │ │ └── validation.vtt │ ├── filters.md │ ├── formats.md │ ├── hydra.md │ ├── install.md │ ├── json-ld.md │ ├── metadata.yml │ ├── open-api-spec.md │ ├── operations.md │ ├── pagination.md │ ├── profiler.md │ ├── property-filter.md │ ├── relation-filtering.md │ ├── relations-iri.md │ ├── relations.md │ ├── serialization-groups.md │ ├── serialization-tricks.md │ ├── serializer.md │ ├── subresources.md │ ├── swagger.md │ ├── user-entity.md │ ├── user-resource.md │ └── validation.md ├── ep2-security │ ├── access-control-voter.md │ ├── access-control.md │ ├── acl-cheese-owner.md │ ├── api-tests.md │ ├── auth-errors.md │ ├── auto-groups.md │ ├── backport-api-tests.md │ ├── conditional-field-setup.md │ ├── context-builder.md │ ├── custom-field.md │ ├── custom-normalizer.md │ ├── custom-validator.md │ ├── data-page-load.md │ ├── encode-user-password.md │ ├── entity-listener.md │ ├── filtered-collection.md │ ├── json-login.md │ ├── login-response.md │ ├── login-success.md │ ├── metadata.yml │ ├── normalizer-aware.md │ ├── normalizer-chain.md │ ├── owner-cleanup.md │ ├── plain-password.md │ ├── previous-object.md │ ├── production-docs.md │ ├── query-extension.md │ ├── query-item-extension.md │ ├── resource-metadata-factory.md │ ├── rock-solid-test-setup.md │ ├── samesite-csrf.md │ ├── service-decoration.md │ ├── test-reset-database.md │ ├── test-roles-reload.md │ ├── test-setup.md │ ├── test-user-login.md │ ├── tokens-cookies.md │ ├── uncached-metadata.md │ ├── validation-groups.md │ └── validator-logic.md └── ep3-custom-data │ ├── apply-filter.md │ ├── collections-readable-link.md │ ├── custom-filter.md │ ├── custom-resource-filter.md │ ├── custom-resource-item.md │ ├── custom-resource-put.md │ ├── custom-resource.md │ ├── data-provider.md │ ├── data-transformer.md │ ├── decorate-provider.md │ ├── decoration-deep-dive.md │ ├── denormalize-iris.md │ ├── dto-embedded-quirks.md │ ├── dto-organization.md │ ├── dto-quirks.md │ ├── dto-update-problems.md │ ├── embedded-normalization.md │ ├── entity-filter-logic.md │ ├── filter-arguments.md │ ├── filter-autowiring.md │ ├── input-dto-validation.md │ ├── input-dto.md │ ├── input-initializer-logic.md │ ├── input-initializer.md │ ├── input-transformer.md │ ├── is-me-field.md │ ├── item-data-provider.md │ ├── listeners-data.md │ ├── metadata.yml │ ├── output-class.md │ ├── output-fields.md │ ├── pagination-context.md │ ├── paginator.md │ ├── persister-context.md │ ├── persister-decoration.md │ ├── post-load-listener.md │ ├── property-metadata.md │ ├── publish-state-change.md │ ├── published-validator.md │ ├── resource-collection.md │ ├── set-uuid.md │ ├── set-via-listener.md │ ├── setting-published.md │ ├── setup.md │ ├── type-validation.md │ ├── uuid-identifier.md │ ├── uuid-quirk.md │ ├── uuid.md │ └── validator-logic.md ├── src ├── Controller │ └── .gitignore └── Kernel.php └── symfony.lock /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the later taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_ENV=dev 18 | APP_SECRET=1c78f3e5e06eb3c88a52819fbccdba6e 19 | #TRUSTED_PROXIES=127.0.0.1,127.0.0.2 20 | #TRUSTED_HOSTS='^localhost|example\.com$' 21 | ###< symfony/framework-bundle ### 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /public/bundles/ 7 | /var/ 8 | /vendor/ 9 | ###< symfony/framework-bundle ### 10 | -------------------------------------------------------------------------------- /_tuts/README.md: -------------------------------------------------------------------------------- 1 | # Hello there! 2 | 3 | The files in this directory cannot be modified directly: we use an internal tool 4 | to manage them. If you find an issue with the code, you can open an issue on the 5 | repository. In fact, that would be awesome :). 6 | -------------------------------------------------------------------------------- /_tuts/acl-allow-admin-to-change.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/IsValidOwnerValidator.php b/src/Validator/IsValidOwnerValidator.php 2 | index 79a51ae..2eee100 100644 3 | --- a/src/Validator/IsValidOwnerValidator.php 4 | +++ b/src/Validator/IsValidOwnerValidator.php 5 | @@ -32,6 +32,11 @@ class IsValidOwnerValidator extends ConstraintValidator 6 | return; 7 | } 8 | 9 | + // allow admin users to change owners 10 | + if ($this->security->isGranted('ROLE_ADMIN')) { 11 | + return; 12 | + } 13 | + 14 | if (!$value instanceof User) { 15 | throw new \InvalidArgumentException('@IsValidOwner constraint must be put on a property containing a User object'); 16 | } 17 | -------------------------------------------------------------------------------- /_tuts/acl-group-to-only-allow-cl-owner-on-post.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 0f4fa89..b3a850d 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -95,8 +95,7 @@ class CheeseListing 6 | /** 7 | * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="cheeseListings") 8 | * @ORM\JoinColumn(nullable=false) 9 | - * @Groups({"cheese:read", "cheese:write"}) 10 | - * @Assert\Valid() 11 | + * @Groups({"cheese:read", "cheese:collection:post"}) 12 | */ 13 | private $owner; 14 | 15 | diff --git a/src/Entity/User.php b/src/Entity/User.php 16 | index 9ce01a1..59ced90 100644 17 | --- a/src/Entity/User.php 18 | +++ b/src/Entity/User.php 19 | @@ -66,7 +66,7 @@ class User implements UserInterface 20 | 21 | /** 22 | * @ORM\Column(type="string", length=255, unique=true) 23 | - * @Groups({"user:read", "user:write", "cheese:item:get", "cheese:write"}) 24 | + * @Groups({"user:read", "user:write", "cheese:item:get"}) 25 | * @Assert\NotBlank() 26 | */ 27 | private $username; 28 | -------------------------------------------------------------------------------- /_tuts/auto-owner-add-tag.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/services.yaml b/config/services.yaml 2 | index 1102e44..95444ae 100644 3 | --- a/config/services.yaml 4 | +++ b/config/services.yaml 5 | @@ -38,3 +38,6 @@ services: 6 | # implications! 7 | decoration_priority: -20 8 | arguments: ['@App\ApiPlatform\AutoGroupResourceMetadataFactory.inner'] 9 | + 10 | + App\Doctrine\CheeseListingSetOwnerListener: 11 | + tags: [doctrine.orm.entity_listener] 12 | \ No newline at end of file 13 | -------------------------------------------------------------------------------- /_tuts/auto-owner-create-listener.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Doctrine/CheeseListingSetOwnerListener.php b/src/Doctrine/CheeseListingSetOwnerListener.php 2 | new file mode 100644 3 | index 0000000..16483a5 4 | --- /dev/null 5 | +++ b/src/Doctrine/CheeseListingSetOwnerListener.php 6 | @@ -0,0 +1,12 @@ 7 | +security = $security; 18 | + } 19 | + 20 | public function prePersist(CheeseListing $cheeseListing) 21 | { 22 | + if ($cheeseListing->getOwner()) { 23 | + return; 24 | + } 25 | + 26 | + if ($this->security->getUser()) { 27 | + $cheeseListing->setOwner($this->security->getUser()); 28 | + } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /_tuts/autogroups-decorates-priority.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/services.yaml b/config/services.yaml 2 | index 577fcf3..1102e44 100644 3 | --- a/config/services.yaml 4 | +++ b/config/services.yaml 5 | @@ -33,4 +33,8 @@ services: 6 | 7 | App\ApiPlatform\AutoGroupResourceMetadataFactory: 8 | decorates: 'api_platform.metadata.resource.metadata_factory' 9 | + # causes this to decorate around the cached factory so that 10 | + # our service is never cached (which, of course, can have performance 11 | + # implications! 12 | + decoration_priority: -20 13 | arguments: ['@App\ApiPlatform\AutoGroupResourceMetadataFactory.inner'] 14 | -------------------------------------------------------------------------------- /_tuts/customitem-dump-id.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsProvider.php b/src/DataProvider/DailyStatsProvider.php 2 | index b4fce51..a2e35ca 100644 3 | --- a/src/DataProvider/DailyStatsProvider.php 4 | +++ b/src/DataProvider/DailyStatsProvider.php 5 | @@ -26,7 +26,7 @@ class DailyStatsProvider implements CollectionDataProviderInterface, ItemDataPro 6 | 7 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 8 | { 9 | - 10 | + dd($id); 11 | } 12 | 13 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 14 | -------------------------------------------------------------------------------- /_tuts/customitem-finish-getitem.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsProvider.php b/src/DataProvider/DailyStatsProvider.php 2 | index a2e35ca..5c452c9 100644 3 | --- a/src/DataProvider/DailyStatsProvider.php 4 | +++ b/src/DataProvider/DailyStatsProvider.php 5 | @@ -26,7 +26,7 @@ class DailyStatsProvider implements CollectionDataProviderInterface, ItemDataPro 6 | 7 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 8 | { 9 | - dd($id); 10 | + return $this->statsHelper->fetchOne($id); 11 | } 12 | 13 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 14 | -------------------------------------------------------------------------------- /_tuts/customitem-reactivate-get-item.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index 804e81d..aa7153d 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -11,12 +11,7 @@ use Symfony\Component\Serializer\Annotation\Groups; 6 | * @ApiResource( 7 | * normalizationContext={"groups"={"daily-stats:read"}}, 8 | * itemOperations={ 9 | - * "get"={ 10 | - * "method"="GET", 11 | - * "controller"=NotFoundAction::class, 12 | - * "read"=false, 13 | - * "output"=false, 14 | - * }, 15 | + * "get", 16 | * }, 17 | * collectionOperations={"get"} 18 | * ) 19 | -------------------------------------------------------------------------------- /_tuts/customput-add-empty-data-persister.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/DailyStatsPersister.php b/src/DataPersister/DailyStatsPersister.php 2 | new file mode 100644 3 | index 0000000..d5706cb 4 | --- /dev/null 5 | +++ b/src/DataPersister/DailyStatsPersister.php 6 | @@ -0,0 +1,20 @@ 7 | +logger = $logger; 18 | + } 19 | + 20 | public function supports($data): bool 21 | { 22 | return $data instanceof DailyStats; 23 | } 24 | 25 | + /** 26 | + * @param DailyStats $data 27 | + */ 28 | public function persist($data) 29 | { 30 | + $this->logger->info(sprintf('Update the visitors to "%d"', $data->totalVisitors)); 31 | } 32 | 33 | public function remove($data) 34 | -------------------------------------------------------------------------------- /_tuts/customresource-add-getter-for-identifier.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index 35863e4..a5481e1 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -21,12 +21,17 @@ use ApiPlatform\Core\Action\NotFoundAction; 6 | */ 7 | class DailyStats 8 | { 9 | - /** 10 | - * @ApiProperty(identifier=true) 11 | - */ 12 | public $date; 13 | 14 | public $totalVisitors; 15 | 16 | public $mostPopularListings; 17 | + 18 | + /** 19 | + * @ApiProperty(identifier=true) 20 | + */ 21 | + public function getDateString(): string 22 | + { 23 | + return $this->date->format('Y-m-d'); 24 | + } 25 | } 26 | -------------------------------------------------------------------------------- /_tuts/customresource-add-item-with-404.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index accd546..e94dc8e 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -3,10 +3,18 @@ 6 | namespace App\Entity; 7 | 8 | use ApiPlatform\Core\Annotation\ApiResource; 9 | +use ApiPlatform\Core\Action\NotFoundAction; 10 | 11 | /** 12 | * @ApiResource( 13 | - * itemOperations={}, 14 | + * itemOperations={ 15 | + * "get"={ 16 | + * "method"="GET", 17 | + * "controller"=NotFoundAction::class, 18 | + * "read"=false, 19 | + * "output"=false, 20 | + * }, 21 | + * }, 22 | * collectionOperations={"get"} 23 | * ) 24 | */ 25 | -------------------------------------------------------------------------------- /_tuts/customresource-add-normalization-groups.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index a5481e1..2e0c158 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -5,9 +5,11 @@ namespace App\Entity; 6 | use ApiPlatform\Core\Annotation\ApiProperty; 7 | use ApiPlatform\Core\Annotation\ApiResource; 8 | use ApiPlatform\Core\Action\NotFoundAction; 9 | +use Symfony\Component\Serializer\Annotation\Groups; 10 | 11 | /** 12 | * @ApiResource( 13 | + * normalizationContext={"groups"={"daily-stats:read"}}, 14 | * itemOperations={ 15 | * "get"={ 16 | * "method"="GET", 17 | @@ -21,10 +23,19 @@ use ApiPlatform\Core\Action\NotFoundAction; 18 | */ 19 | class DailyStats 20 | { 21 | + /** 22 | + * @Groups({"daily-stats:read"}) 23 | + */ 24 | public $date; 25 | 26 | + /** 27 | + * @Groups({"daily-stats:read"}) 28 | + */ 29 | public $totalVisitors; 30 | 31 | + /** 32 | + * @Groups({"daily-stats:read"}) 33 | + */ 34 | public $mostPopularListings; 35 | 36 | /** 37 | -------------------------------------------------------------------------------- /_tuts/customresource-create-dailystats.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | new file mode 100644 3 | index 0000000..e37edc1 4 | --- /dev/null 5 | +++ b/src/Entity/DailyStats.php 6 | @@ -0,0 +1,17 @@ 7 | +date = new \DateTime(); 17 | + $stats->totalVisitors = 1000; 18 | + 19 | + return [$stats]; 20 | } 21 | 22 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 23 | { 24 | + return $resourceClass === DailyStats::class; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /_tuts/customresource-set-identifier-to-date.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index e94dc8e..35863e4 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -2,6 +2,7 @@ 6 | 7 | namespace App\Entity; 8 | 9 | +use ApiPlatform\Core\Annotation\ApiProperty; 10 | use ApiPlatform\Core\Annotation\ApiResource; 11 | use ApiPlatform\Core\Action\NotFoundAction; 12 | 13 | @@ -20,6 +21,9 @@ use ApiPlatform\Core\Action\NotFoundAction; 14 | */ 15 | class DailyStats 16 | { 17 | + /** 18 | + * @ApiProperty(identifier=true) 19 | + */ 20 | public $date; 21 | 22 | public $totalVisitors; 23 | -------------------------------------------------------------------------------- /_tuts/delete-me-1.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-1.diff -------------------------------------------------------------------------------- /_tuts/delete-me-10.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-10.diff -------------------------------------------------------------------------------- /_tuts/delete-me-3.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-3.diff -------------------------------------------------------------------------------- /_tuts/delete-me-5.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-5.diff -------------------------------------------------------------------------------- /_tuts/delete-me-6.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-6.diff -------------------------------------------------------------------------------- /_tuts/delete-me-7.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-7.diff -------------------------------------------------------------------------------- /_tuts/delete-me-8.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-8.diff -------------------------------------------------------------------------------- /_tuts/delete-me-9.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/delete-me-9.diff -------------------------------------------------------------------------------- /_tuts/dtofilter-dd-in-apply.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/DailyStatsDateFilter.php b/src/ApiPlatform/DailyStatsDateFilter.php 2 | index 430239e..c989b95 100644 3 | --- a/src/ApiPlatform/DailyStatsDateFilter.php 4 | +++ b/src/ApiPlatform/DailyStatsDateFilter.php 5 | @@ -9,6 +9,7 @@ class DailyStatsDateFilter implements FilterInterface 6 | { 7 | public function apply(Request $request, bool $normalization, array $attributes, array &$context) 8 | { 9 | + dd($request->query->all()); 10 | } 11 | 12 | public function getDescription(string $resourceClass): array 13 | -------------------------------------------------------------------------------- /_tuts/dtofilter-dump-from-query-param.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/DailyStatsDateFilter.php b/src/ApiPlatform/DailyStatsDateFilter.php 2 | index c989b95..ae38657 100644 3 | --- a/src/ApiPlatform/DailyStatsDateFilter.php 4 | +++ b/src/ApiPlatform/DailyStatsDateFilter.php 5 | @@ -9,7 +9,13 @@ class DailyStatsDateFilter implements FilterInterface 6 | { 7 | public function apply(Request $request, bool $normalization, array $attributes, array &$context) 8 | { 9 | - dd($request->query->all()); 10 | + $from = $request->query->get('from'); 11 | + 12 | + if (!$from) { 13 | + return; 14 | + } 15 | + 16 | + dd($from); 17 | } 18 | 19 | public function getDescription(string $resourceClass): array 20 | -------------------------------------------------------------------------------- /_tuts/dtofilter-fill-in-getdescription.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/DailyStatsDateFilter.php b/src/ApiPlatform/DailyStatsDateFilter.php 2 | index e79a08f..430239e 100644 3 | --- a/src/ApiPlatform/DailyStatsDateFilter.php 4 | +++ b/src/ApiPlatform/DailyStatsDateFilter.php 5 | @@ -13,5 +13,15 @@ class DailyStatsDateFilter implements FilterInterface 6 | 7 | public function getDescription(string $resourceClass): array 8 | { 9 | + return [ 10 | + 'from' => [ 11 | + 'property' => null, 12 | + 'type' => 'string', 13 | + 'required' => false, 14 | + 'openapi' => [ 15 | + 'description' => 'From date e.g. 2020-09-01', 16 | + ], 17 | + ] 18 | + ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /_tuts/dtofilter-pass-in-hardcoded-from.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsProvider.php b/src/DataProvider/DailyStatsProvider.php 2 | index 1d2b83f..900814d 100644 3 | --- a/src/DataProvider/DailyStatsProvider.php 4 | +++ b/src/DataProvider/DailyStatsProvider.php 5 | @@ -26,11 +26,14 @@ class DailyStatsProvider implements CollectionDataProviderInterface, ItemDataPro 6 | { 7 | list($page, $offset, $limit) = $this->pagination->getPagination($resourceClass, $operationName); 8 | 9 | - return new DailyStatsPaginator( 10 | + $paginator = new DailyStatsPaginator( 11 | $this->statsHelper, 12 | $page, 13 | $limit 14 | ); 15 | + $paginator->setFromDate(new \DateTime('2020-08-30')); 16 | + 17 | + return $paginator; 18 | } 19 | 20 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 21 | -------------------------------------------------------------------------------- /_tuts/dtofilter-return-400-error.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/DailyStatsDateFilter.php b/src/ApiPlatform/DailyStatsDateFilter.php 2 | index 0c11287..94e193a 100644 3 | --- a/src/ApiPlatform/DailyStatsDateFilter.php 4 | +++ b/src/ApiPlatform/DailyStatsDateFilter.php 5 | @@ -4,6 +4,7 @@ namespace App\ApiPlatform; 6 | 7 | use ApiPlatform\Core\Serializer\Filter\FilterInterface; 8 | use Symfony\Component\HttpFoundation\Request; 9 | +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; 10 | 11 | class DailyStatsDateFilter implements FilterInterface 12 | { 13 | @@ -19,6 +20,11 @@ class DailyStatsDateFilter implements FilterInterface 14 | 15 | $fromDate = \DateTimeImmutable::createFromFormat('Y-m-d', $from); 16 | 17 | + // you could optionally return a 400 error 18 | + if (!$fromDate) { 19 | + throw new BadRequestHttpException('Invalid "from" date format'); 20 | + } 21 | + 22 | if ($fromDate) { 23 | $fromDate = $fromDate->setTime(0, 0, 0); 24 | $context[self::FROM_FILTER_CONTEXT] = $fromDate; 25 | -------------------------------------------------------------------------------- /_tuts/dtofilter-set-date-on-context.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/DailyStatsDateFilter.php b/src/ApiPlatform/DailyStatsDateFilter.php 2 | index ae38657..0c11287 100644 3 | --- a/src/ApiPlatform/DailyStatsDateFilter.php 4 | +++ b/src/ApiPlatform/DailyStatsDateFilter.php 5 | @@ -7,6 +7,8 @@ use Symfony\Component\HttpFoundation\Request; 6 | 7 | class DailyStatsDateFilter implements FilterInterface 8 | { 9 | + public const FROM_FILTER_CONTEXT = 'daily_stats_from'; 10 | + 11 | public function apply(Request $request, bool $normalization, array $attributes, array &$context) 12 | { 13 | $from = $request->query->get('from'); 14 | @@ -15,7 +17,12 @@ class DailyStatsDateFilter implements FilterInterface 15 | return; 16 | } 17 | 18 | - dd($from); 19 | + $fromDate = \DateTimeImmutable::createFromFormat('Y-m-d', $from); 20 | + 21 | + if ($fromDate) { 22 | + $fromDate = $fromDate->setTime(0, 0, 0); 23 | + $context[self::FROM_FILTER_CONTEXT] = $fromDate; 24 | + } 25 | } 26 | 27 | public function getDescription(string $resourceClass): array 28 | -------------------------------------------------------------------------------- /_tuts/dtoinput-add-class.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 3526428..ff2d4e8 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -9,6 +9,7 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter; 6 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; 7 | use ApiPlatform\Core\Serializer\Filter\PropertyFilter; 8 | use App\ApiPlatform\CheeseSearchFilter; 9 | +use App\Dto\CheeseListingInput; 10 | use App\Dto\CheeseListingOutput; 11 | use App\Validator\IsValidOwner; 12 | use App\Validator\ValidIsPublished; 13 | @@ -20,6 +21,7 @@ use Symfony\Component\Validator\Constraints as Assert; 14 | /** 15 | * @ApiResource( 16 | * output=CheeseListingOutput::CLASS, 17 | + * input=CheeseListingInput::CLASS, 18 | * normalizationContext={"groups"={"cheese:read"}}, 19 | * denormalizationContext={"groups"={"cheese:write"}}, 20 | * itemOperations={ 21 | -------------------------------------------------------------------------------- /_tuts/dtoinput-add-phpdoc.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataTransformer/CheeseListingInputDataTransformer.php b/src/DataTransformer/CheeseListingInputDataTransformer.php 2 | index 43d8e51..3015b7a 100644 3 | --- a/src/DataTransformer/CheeseListingInputDataTransformer.php 4 | +++ b/src/DataTransformer/CheeseListingInputDataTransformer.php 5 | @@ -8,9 +8,12 @@ use App\Entity\CheeseListing; 6 | 7 | class CheeseListingInputDataTransformer implements DataTransformerInterface 8 | { 9 | - public function transform($object, string $to, array $context = []) 10 | + /** 11 | + * @param CheeseListingInput $input 12 | + */ 13 | + public function transform($input, string $to, array $context = []) 14 | { 15 | - dump($object, $to, $context); 16 | + dump($input, $to, $context); 17 | 18 | return new CheeseListing(); 19 | } 20 | -------------------------------------------------------------------------------- /_tuts/dtoinput-add-var-for-other-props.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingInput.php b/src/Dto/CheeseListingInput.php 2 | index de80fad..a0a4fca 100644 3 | --- a/src/Dto/CheeseListingInput.php 4 | +++ b/src/Dto/CheeseListingInput.php 5 | @@ -9,11 +9,13 @@ use Symfony\Component\Serializer\Annotation\SerializedName; 6 | class CheeseListingInput 7 | { 8 | /** 9 | + * @var string 10 | * @Groups({"cheese:write", "user:write"}) 11 | */ 12 | public $title; 13 | 14 | /** 15 | + * @var int 16 | * @Groups({"cheese:write", "user:write"}) 17 | */ 18 | public $price; 19 | @@ -25,6 +27,7 @@ class CheeseListingInput 20 | public $owner; 21 | 22 | /** 23 | + * @var bool 24 | * @Groups({"cheese:write"}) 25 | */ 26 | public $isPublished = false; 27 | -------------------------------------------------------------------------------- /_tuts/dtoinput-add-var-user.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingInput.php b/src/Dto/CheeseListingInput.php 2 | index 30035aa..de80fad 100644 3 | --- a/src/Dto/CheeseListingInput.php 4 | +++ b/src/Dto/CheeseListingInput.php 5 | @@ -2,6 +2,7 @@ 6 | 7 | namespace App\Dto; 8 | 9 | +use App\Entity\User; 10 | use Symfony\Component\Serializer\Annotation\Groups; 11 | use Symfony\Component\Serializer\Annotation\SerializedName; 12 | 13 | @@ -18,6 +19,7 @@ class CheeseListingInput 14 | public $price; 15 | 16 | /** 17 | + * @var User 18 | * @Groups({"cheese:collection:post"}) 19 | */ 20 | public $owner; 21 | -------------------------------------------------------------------------------- /_tuts/dtoinput-complete-transform.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataTransformer/CheeseListingInputDataTransformer.php b/src/DataTransformer/CheeseListingInputDataTransformer.php 2 | index 3015b7a..79e36cb 100644 3 | --- a/src/DataTransformer/CheeseListingInputDataTransformer.php 4 | +++ b/src/DataTransformer/CheeseListingInputDataTransformer.php 5 | @@ -15,7 +15,13 @@ class CheeseListingInputDataTransformer implements DataTransformerInterface 6 | { 7 | dump($input, $to, $context); 8 | 9 | - return new CheeseListing(); 10 | + $cheeseListing = new CheeseListing($input->title); 11 | + $cheeseListing->setDescription($input->description); 12 | + $cheeseListing->setPrice($input->price); 13 | + $cheeseListing->setOwner($input->owner); 14 | + $cheeseListing->setIsPublished($input->isPublished); 15 | + 16 | + return $cheeseListing; 17 | } 18 | 19 | public function supportsTransformation($data, string $to, array $context = []): bool 20 | -------------------------------------------------------------------------------- /_tuts/dtoinput-dump-arguments.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataTransformer/CheeseListingInputDataTransformer.php b/src/DataTransformer/CheeseListingInputDataTransformer.php 2 | index 2d63e9b..116b9c8 100644 3 | --- a/src/DataTransformer/CheeseListingInputDataTransformer.php 4 | +++ b/src/DataTransformer/CheeseListingInputDataTransformer.php 5 | @@ -12,5 +12,8 @@ class CheeseListingInputDataTransformer implements DataTransformerInterface 6 | 7 | public function supportsTransformation($data, string $to, array $context = []): bool 8 | { 9 | + dump($data, $to, $context); 10 | + 11 | + return false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /_tuts/dtoinput-empty-transformer.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataTransformer/CheeseListingInputDataTransformer.php b/src/DataTransformer/CheeseListingInputDataTransformer.php 2 | new file mode 100644 3 | index 0000000..2d63e9b 4 | --- /dev/null 5 | +++ b/src/DataTransformer/CheeseListingInputDataTransformer.php 6 | @@ -0,0 +1,16 @@ 7 | + 13 | -------------------------------------------------------------------------------- /_tuts/dtonormalizeissue-add-user-read-groups.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingOutput.php b/src/Dto/CheeseListingOutput.php 2 | index e3a2a93..42ddb37 100644 3 | --- a/src/Dto/CheeseListingOutput.php 4 | +++ b/src/Dto/CheeseListingOutput.php 5 | @@ -11,7 +11,7 @@ class CheeseListingOutput 6 | /** 7 | * The title of this listing 8 | * 9 | - * @Groups({"cheese:read"}) 10 | + * @Groups({"cheese:read", "user:read"}) 11 | * @var string 12 | */ 13 | public $title; 14 | @@ -24,7 +24,7 @@ class CheeseListingOutput 15 | 16 | /** 17 | * @var integer 18 | - * @Groups({"cheese:read"}) 19 | + * @Groups({"cheese:read", "user:read"}) 20 | */ 21 | public $price; 22 | 23 | -------------------------------------------------------------------------------- /_tuts/dtonormalizeissue-re-add-readablelink.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 26bfd6f..5f5f8dd 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -209,6 +209,7 @@ class User implements UserInterface 6 | } 7 | 8 | /** 9 | + * @ApiProperty(readableLink=true) 10 | * @Groups({"user:read"}) 11 | * @SerializedName("cheeseListings") 12 | * @return Collection 13 | -------------------------------------------------------------------------------- /_tuts/dtonormalizeissue-remove-readablelink.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 0353861..26bfd6f 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -209,7 +209,6 @@ class User implements UserInterface 6 | } 7 | 8 | /** 9 | - * @ApiProperty(readableLink=false) 10 | * @Groups({"user:read"}) 11 | * @SerializedName("cheeseListings") 12 | * @return Collection 13 | -------------------------------------------------------------------------------- /_tuts/dtooption-add-throwoninvalid.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/DailyStatsDateFilter.php b/src/ApiPlatform/DailyStatsDateFilter.php 2 | index 94e193a..9e8b749 100644 3 | --- a/src/ApiPlatform/DailyStatsDateFilter.php 4 | +++ b/src/ApiPlatform/DailyStatsDateFilter.php 5 | @@ -10,6 +10,13 @@ class DailyStatsDateFilter implements FilterInterface 6 | { 7 | public const FROM_FILTER_CONTEXT = 'daily_stats_from'; 8 | 9 | + private $throwOnInvalid; 10 | + 11 | + public function __construct(bool $throwOnInvalid = false) 12 | + { 13 | + $this->throwOnInvalid = $throwOnInvalid; 14 | + } 15 | + 16 | public function apply(Request $request, bool $normalization, array $attributes, array &$context) 17 | { 18 | $from = $request->query->get('from'); 19 | @@ -21,7 +28,7 @@ class DailyStatsDateFilter implements FilterInterface 20 | $fromDate = \DateTimeImmutable::createFromFormat('Y-m-d', $from); 21 | 22 | // you could optionally return a 400 error 23 | - if (!$fromDate) { 24 | + if (!$fromDate && $this->throwOnInvalid) { 25 | throw new BadRequestHttpException('Invalid "from" date format'); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /_tuts/dtooption-pass-throwoninvalid.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index 7786c2f..2bb8731 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -20,7 +20,7 @@ use Symfony\Component\Serializer\Annotation\Groups; 6 | * }, 7 | * collectionOperations={"get"} 8 | * ) 9 | - * @ApiFilter(DailyStatsDateFilter::class) 10 | + * @ApiFilter(DailyStatsDateFilter::class, arguments={"throwOnInvalid"=true}) 11 | */ 12 | class DailyStats 13 | { 14 | -------------------------------------------------------------------------------- /_tuts/dtooutput-add-empty-transformer.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataTransformer/CheeseListingOutputDataTransformer.php b/src/DataTransformer/CheeseListingOutputDataTransformer.php 2 | new file mode 100644 3 | index 0000000..51635ce 4 | --- /dev/null 5 | +++ b/src/DataTransformer/CheeseListingOutputDataTransformer.php 6 | @@ -0,0 +1,20 @@ 7 | +title = $cheeseListing->getTitle(); 18 | + 19 | + return $output; 20 | } 21 | 22 | public function supportsTransformation($data, string $to, array $context = []): bool 23 | -------------------------------------------------------------------------------- /_tuts/dtooutput-copy-exposed-getter.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingOutput.php b/src/Dto/CheeseListingOutput.php 2 | index 40e1229..be0b875 100644 3 | --- a/src/Dto/CheeseListingOutput.php 4 | +++ b/src/Dto/CheeseListingOutput.php 5 | @@ -25,4 +25,16 @@ class CheeseListingOutput 6 | * @Groups({"cheese:read"}) 7 | */ 8 | public $price; 9 | + 10 | + /** 11 | + * @Groups("cheese:read") 12 | + */ 13 | + public function getShortDescription(): ?string 14 | + { 15 | + if (strlen($this->description) < 40) { 16 | + return $this->description; 17 | + } 18 | + 19 | + return substr($this->description, 0, 40).'...'; 20 | + } 21 | } 22 | -------------------------------------------------------------------------------- /_tuts/dtooutput-copy-other-method.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingOutput.php b/src/Dto/CheeseListingOutput.php 2 | index be0b875..b282b0c 100644 3 | --- a/src/Dto/CheeseListingOutput.php 4 | +++ b/src/Dto/CheeseListingOutput.php 5 | @@ -2,6 +2,7 @@ 6 | 7 | namespace App\Dto; 8 | 9 | +use Carbon\Carbon; 10 | use Symfony\Component\Serializer\Annotation\Groups; 11 | 12 | class CheeseListingOutput 13 | @@ -37,4 +38,14 @@ class CheeseListingOutput 14 | 15 | return substr($this->description, 0, 40).'...'; 16 | } 17 | + 18 | + /** 19 | + * How long ago in text that this cheese listing was added. 20 | + * 21 | + * @Groups("cheese:read") 22 | + */ 23 | + public function getCreatedAtAgo(): string 24 | + { 25 | + return Carbon::instance($this->getCreatedAt())->diffForHumans(); 26 | + } 27 | } 28 | -------------------------------------------------------------------------------- /_tuts/dtooutput-create-basic-cheeselistingoutput.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingOutput.php b/src/Dto/CheeseListingOutput.php 2 | new file mode 100644 3 | index 0000000..b2ad541 4 | --- /dev/null 5 | +++ b/src/Dto/CheeseListingOutput.php 6 | @@ -0,0 +1,8 @@ 7 | +assertJsonContains(['hydra:member' => [ 7 | 0 => [ 8 | '@id' => '/api/cheeses/' . $cheeseListing2->getId(), 9 | - '@type' => 'cheese', 10 | 'title' => 'cheese2', 11 | 'description' => 'cheese', 12 | 'price' => 1000, 13 | -------------------------------------------------------------------------------- /_tuts/dtoownerprop-fix-usernormalizer.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Serializer/Normalizer/UserNormalizer.php b/src/Serializer/Normalizer/UserNormalizer.php 2 | index 902fc95..744674e 100644 3 | --- a/src/Serializer/Normalizer/UserNormalizer.php 4 | +++ b/src/Serializer/Normalizer/UserNormalizer.php 5 | @@ -25,7 +25,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, CacheableSuppor 6 | /** 7 | * @param User $object 8 | */ 9 | - public function normalize($object, $format = null, array $context = array()): array 10 | + public function normalize($object, $format = null, array $context = array()) 11 | { 12 | $isOwner = $this->userIsOwner($object); 13 | if ($isOwner) { 14 | -------------------------------------------------------------------------------- /_tuts/dtoupdate-empty-denormalizer.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Serializer/Normalizer/CheeseListingInputDenormalizer.php b/src/Serializer/Normalizer/CheeseListingInputDenormalizer.php 2 | new file mode 100644 3 | index 0000000..13b1ced 4 | --- /dev/null 5 | +++ b/src/Serializer/Normalizer/CheeseListingInputDenormalizer.php 6 | @@ -0,0 +1,21 @@ 7 | +title = 'I am set in the denormalizer!'; 11 | - 12 | - $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $dto; 13 | + $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $this->createDto($context); 14 | 15 | return $this->objectNormalizer->denormalize($data, $type, $format, $context); 16 | } 17 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-add-phonenumber.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index e61789d..494eec1 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -87,6 +87,11 @@ class User implements UserInterface 6 | */ 7 | private $plainPassword; 8 | 9 | + /** 10 | + * @ORM\Column(type="string", length=50, nullable=true) 11 | + */ 12 | + private $phoneNumber; 13 | + 14 | public function __construct() 15 | { 16 | $this->cheeseListings = new ArrayCollection(); 17 | @@ -219,4 +224,16 @@ class User implements UserInterface 18 | 19 | return $this; 20 | } 21 | + 22 | + public function getPhoneNumber(): ?string 23 | + { 24 | + return $this->phoneNumber; 25 | + } 26 | + 27 | + public function setPhoneNumber(?string $phoneNumber): self 28 | + { 29 | + $this->phoneNumber = $phoneNumber; 30 | + 31 | + return $this; 32 | + } 33 | } 34 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-change-to-admin-write.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 900502b..92a5066 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -56,7 +56,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="json") 9 | - * @Groups({"user:write"}) 10 | + * @Groups({"admin:write"}) 11 | */ 12 | private $roles = []; 13 | 14 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-chnage-to-admin-read.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 403b904..900502b 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -89,7 +89,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="string", length=50, nullable=true) 9 | - * @Groups({"user:read", "user:write"}) 10 | + * @Groups({"admin:read", "user:write"}) 11 | */ 12 | private $phoneNumber; 13 | 14 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-groups-for-phonenumber.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 494eec1..403b904 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -89,6 +89,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="string", length=50, nullable=true) 9 | + * @Groups({"user:read", "user:write"}) 10 | */ 11 | private $phoneNumber; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-input-group-for-user-roles.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 2a52020..e61789d 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -56,6 +56,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="json") 9 | + * @Groups({"user:write"}) 10 | */ 11 | private $roles = []; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-protect-roles.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 92a5066..e3bdf01 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -56,7 +56,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="json") 9 | - * @Groups({"admin:write"}) 10 | + * @Groups({"admin:read", "admin:write"}) 11 | */ 12 | private $roles = []; 13 | 14 | -------------------------------------------------------------------------------- /_tuts/dynamserialization-test-for-roles-update.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index 1d8c420..abbb1e0 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -33,13 +33,19 @@ class UserResourceTest extends CustomApiTestCase 6 | 7 | $client->request('PUT', '/api/users/'.$user->getId(), [ 8 | 'json' => [ 9 | - 'username' => 'newusername' 10 | + 'username' => 'newusername', 11 | + 'roles' => ['ROLE_ADMIN'] // will be ignored 12 | ] 13 | ]); 14 | $this->assertResponseIsSuccessful(); 15 | $this->assertJsonContains([ 16 | 'username' => 'newusername' 17 | ]); 18 | + 19 | + $em = $this->getEntityManager(); 20 | + /** @var User $user */ 21 | + $user = $em->getRepository(User::class)->find($user->getId()); 22 | + $this->assertEquals(['ROLE_USER'], $user->getRoles()); 23 | } 24 | 25 | public function testGetUser() 26 | -------------------------------------------------------------------------------- /_tuts/embeddednormalization-advertise-return-type.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 72ee63b..ebb1559 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -210,6 +210,7 @@ class User implements UserInterface 6 | /** 7 | * @Groups({"user:read"}) 8 | * SerializedName("cheeseListings") 9 | + * @return Collection 10 | */ 11 | public function getPublishedCheeseListings(): Collection 12 | { 13 | -------------------------------------------------------------------------------- /_tuts/embeddednormalization-help-my-editor.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index d445457..804e81d 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -36,7 +36,7 @@ class DailyStats 6 | /** 7 | * The 5 most popular cheese listings from this date! 8 | * 9 | - * @var array 10 | + * @var array|CheeseListing[] 11 | * @Groups({"daily-stats:read"}) 12 | */ 13 | public $mostPopularListings; 14 | -------------------------------------------------------------------------------- /_tuts/embeddednormalization-redo-listing-docs.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/DailyStats.php b/src/Entity/DailyStats.php 2 | index 6f28f68..d445457 100644 3 | --- a/src/Entity/DailyStats.php 4 | +++ b/src/Entity/DailyStats.php 5 | @@ -36,13 +36,11 @@ class DailyStats 6 | /** 7 | * The 5 most popular cheese listings from this date! 8 | * 9 | + * @var array 10 | * @Groups({"daily-stats:read"}) 11 | */ 12 | public $mostPopularListings; 13 | 14 | - /** 15 | - * @param array|CheeseListing[] $mostPopularListings 16 | - */ 17 | public function __construct(\DateTimeInterface $date, int $totalVisitors, array $mostPopularListings) 18 | { 19 | $this->date = $date; 20 | -------------------------------------------------------------------------------- /_tuts/embeddednormalization-return-2-stats.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsProvider.php b/src/DataProvider/DailyStatsProvider.php 2 | index e304226..03c20fa 100644 3 | --- a/src/DataProvider/DailyStatsProvider.php 4 | +++ b/src/DataProvider/DailyStatsProvider.php 5 | @@ -16,7 +16,13 @@ class DailyStatsProvider implements CollectionDataProviderInterface, RestrictedD 6 | [] 7 | ); 8 | 9 | - return [$stats]; 10 | + $stats2 = new DailyStats( 11 | + new \DateTime('-1 day'), 12 | + 2000, 13 | + [] 14 | + ); 15 | + 16 | + return [$stats, $stats2]; 17 | } 18 | 19 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 20 | -------------------------------------------------------------------------------- /_tuts/embeddednormalization-with-readablelink.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index ebb1559..85f963e 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -3,6 +3,7 @@ 6 | namespace App\Entity; 7 | 8 | use ApiPlatform\Core\Annotation\ApiFilter; 9 | +use ApiPlatform\Core\Annotation\ApiProperty; 10 | use ApiPlatform\Core\Annotation\ApiResource; 11 | use ApiPlatform\Core\Serializer\Filter\PropertyFilter; 12 | use App\Doctrine\UserSetIsMvpListener; 13 | @@ -208,6 +209,7 @@ class User implements UserInterface 14 | } 15 | 16 | /** 17 | + * @ApiProperty(readableLink=true) 18 | * @Groups({"user:read"}) 19 | * SerializedName("cheeseListings") 20 | * @return Collection 21 | -------------------------------------------------------------------------------- /_tuts/encoding-add-plainpassword-field.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index d5560e5..106e5ff 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -76,6 +76,11 @@ class User implements UserInterface 6 | */ 7 | private $cheeseListings; 8 | 9 | + /** 10 | + * @Groups("user:write") 11 | + */ 12 | + private $plainPassword; 13 | + 14 | public function __construct() 15 | { 16 | $this->cheeseListings = new ArrayCollection(); 17 | @@ -196,4 +201,16 @@ class User implements UserInterface 18 | 19 | return $this; 20 | } 21 | + 22 | + public function getPlainPassword(): ?string 23 | + { 24 | + return $this->plainPassword; 25 | + } 26 | + 27 | + public function setPlainPassword(string $plainPassword): self 28 | + { 29 | + $this->plainPassword = $plainPassword; 30 | + 31 | + return $this; 32 | + } 33 | } 34 | -------------------------------------------------------------------------------- /_tuts/encoding-bootstrap-data-persister.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/UserDataPersister.php b/src/DataPersister/UserDataPersister.php 2 | new file mode 100644 3 | index 0000000..fb11a3a 4 | --- /dev/null 5 | +++ b/src/DataPersister/UserDataPersister.php 6 | @@ -0,0 +1,23 @@ 7 | +plainPassword = null; 10 | + $this->plainPassword = null; 11 | } 12 | 13 | public function setUsername(string $username): self 14 | -------------------------------------------------------------------------------- /_tuts/encoding-remove-password-from-group.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 106e5ff..0b5e6df 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -58,7 +58,6 @@ class User implements UserInterface 6 | /** 7 | * @var string The hashed password 8 | * @ORM\Column(type="string") 9 | - * @Groups({"user:write"}) 10 | */ 11 | private $password; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/encoding-rename-field-to-password.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index fb341de..8b0cf8e 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -11,6 +11,7 @@ use Doctrine\ORM\Mapping as ORM; 6 | use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 7 | use Symfony\Component\Security\Core\User\UserInterface; 8 | use Symfony\Component\Serializer\Annotation\Groups; 9 | +use Symfony\Component\Serializer\Annotation\SerializedName; 10 | use Symfony\Component\Validator\Constraints as Assert; 11 | 12 | /** 13 | @@ -77,6 +78,7 @@ class User implements UserInterface 14 | 15 | /** 16 | * @Groups("user:write") 17 | + * @SerializedName("password") 18 | */ 19 | private $plainPassword; 20 | 21 | -------------------------------------------------------------------------------- /_tuts/encoding-test-for-user-registration.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | new file mode 100644 3 | index 0000000..eaf88e5 4 | --- /dev/null 5 | +++ b/tests/Functional/UserResourceTest.php 6 | @@ -0,0 +1,27 @@ 7 | +request('POST', '/api/users', [ 23 | + 'json' => [ 24 | + 'email' => 'cheeseplease@example.com', 25 | + 'username' => 'cheeseplease', 26 | + 'password' => 'brie' 27 | + ] 28 | + ]); 29 | + $this->assertResponseStatusCodeSame(201); 30 | + 31 | + $this->logIn($client, 'cheeseplease@example.com', 'brie'); 32 | + } 33 | +} 34 | \ No newline at end of file 35 | -------------------------------------------------------------------------------- /_tuts/entityfilter-add-cheesesearchfilter.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | new file mode 100644 3 | index 0000000..72103fa 4 | --- /dev/null 5 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 6 | @@ -0,0 +1,18 @@ 7 | +properties); 10 | + return [ 11 | + 'search' => [ 12 | + 'property' => null, 13 | + 'type' => 'string', 14 | + 'required' => false, 15 | + ] 16 | + ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /_tuts/entityfilter-add-like-query.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | index 404cf6d..c22069a 100644 3 | --- a/src/ApiPlatform/CheeseSearchFilter.php 4 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 5 | @@ -10,7 +10,13 @@ class CheeseSearchFilter extends AbstractFilter 6 | { 7 | protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) 8 | { 9 | - dd($property, $value); 10 | + if ($property !== 'search') { 11 | + return; 12 | + } 13 | + 14 | + $alias = $queryBuilder->getRootAliases()[0]; 15 | + $queryBuilder->andWhere(sprintf('%s.title LIKE :search OR %s.description LIKE :search', $alias, $alias)) 16 | + ->setParameter('search', '%'.$value.'%'); 17 | } 18 | 19 | public function getDescription(string $resourceClass): array 20 | -------------------------------------------------------------------------------- /_tuts/entityfilter-add-openapi-description.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | index 0dd3753..eb30db1 100644 3 | --- a/src/ApiPlatform/CheeseSearchFilter.php 4 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 5 | @@ -19,6 +19,9 @@ class CheeseSearchFilter extends AbstractFilter 6 | 'property' => null, 7 | 'type' => 'string', 8 | 'required' => false, 9 | + 'openapi' => [ 10 | + 'description' => 'Search across multiple fields', 11 | + ], 12 | ] 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /_tuts/entityfilter-add-properties-option.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index d7ccc68..9593291 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -53,7 +53,7 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * }) 7 | * @ApiFilter(RangeFilter::class, properties={"price"}) 8 | * @ApiFilter(PropertyFilter::class) 9 | - * @ApiFilter(CheeseSearchFilter::class) 10 | + * @ApiFilter(CheeseSearchFilter::class, properties={"price"}) 11 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 12 | * @ORM\EntityListeners({"App\Doctrine\CheeseListingSetOwnerListener"}) 13 | * @ValidIsPublished() 14 | -------------------------------------------------------------------------------- /_tuts/entityfilter-dump-property-and-value.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | index eb30db1..404cf6d 100644 3 | --- a/src/ApiPlatform/CheeseSearchFilter.php 4 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 5 | @@ -10,6 +10,7 @@ class CheeseSearchFilter extends AbstractFilter 6 | { 7 | protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) 8 | { 9 | + dd($property, $value); 10 | } 11 | 12 | public function getDescription(string $resourceClass): array 13 | -------------------------------------------------------------------------------- /_tuts/entityfilter-remove-or.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 9593291..d7ccc68 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -53,7 +53,7 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * }) 7 | * @ApiFilter(RangeFilter::class, properties={"price"}) 8 | * @ApiFilter(PropertyFilter::class) 9 | - * @ApiFilter(CheeseSearchFilter::class, properties={"price"}) 10 | + * @ApiFilter(CheeseSearchFilter::class) 11 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 12 | * @ORM\EntityListeners({"App\Doctrine\CheeseListingSetOwnerListener"}) 13 | * @ValidIsPublished() 14 | -------------------------------------------------------------------------------- /_tuts/entityfilter-use-query-name-generator.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | index c22069a..dc99994 100644 3 | --- a/src/ApiPlatform/CheeseSearchFilter.php 4 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 5 | @@ -15,8 +15,10 @@ class CheeseSearchFilter extends AbstractFilter 6 | } 7 | 8 | $alias = $queryBuilder->getRootAliases()[0]; 9 | - $queryBuilder->andWhere(sprintf('%s.title LIKE :search OR %s.description LIKE :search', $alias, $alias)) 10 | - ->setParameter('search', '%'.$value.'%'); 11 | + // a param name that is guaranteed unique in this query 12 | + $valueParameter = $queryNameGenerator->generateParameterName('search'); 13 | + $queryBuilder->andWhere(sprintf('%s.title LIKE :%s OR %s.description LIKE :%s', $alias, $valueParameter, $alias, $valueParameter)) 14 | + ->setParameter($valueParameter, '%'.$value.'%'); 15 | } 16 | 17 | public function getDescription(string $resourceClass): array 18 | -------------------------------------------------------------------------------- /_tuts/entityfilteroption-add-uselike-argument.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index d7ccc68..bd625a7 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -53,7 +53,7 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * }) 7 | * @ApiFilter(RangeFilter::class, properties={"price"}) 8 | * @ApiFilter(PropertyFilter::class) 9 | - * @ApiFilter(CheeseSearchFilter::class) 10 | + * @ApiFilter(CheeseSearchFilter::class, arguments={"useLike"=true}) 11 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 12 | * @ORM\EntityListeners({"App\Doctrine\CheeseListingSetOwnerListener"}) 13 | * @ValidIsPublished() 14 | -------------------------------------------------------------------------------- /_tuts/entityfilteroption-add-uselike.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | index f6a5da6..982e919 100644 3 | --- a/src/ApiPlatform/CheeseSearchFilter.php 4 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 5 | @@ -12,8 +12,13 @@ use Symfony\Component\Serializer\NameConverter\NameConverterInterface; 6 | 7 | class CheeseSearchFilter extends AbstractFilter 8 | { 9 | - public function __construct(ManagerRegistry $managerRegistry, NameConverterInterface $nameConverter = null) 10 | + private $useLike; 11 | + 12 | + public function __construct(ManagerRegistry $managerRegistry, bool $useLike = false, NameConverterInterface $nameConverter = null) 13 | { 14 | + // todo - actually use this 15 | + $this->useLike = $useLike; 16 | + 17 | parent::__construct($managerRegistry, null, null, [], $nameConverter); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /_tuts/entityfilteroption-simplify-constructor.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseSearchFilter.php b/src/ApiPlatform/CheeseSearchFilter.php 2 | index adf2152..f6a5da6 100644 3 | --- a/src/ApiPlatform/CheeseSearchFilter.php 4 | +++ b/src/ApiPlatform/CheeseSearchFilter.php 5 | @@ -12,9 +12,9 @@ use Symfony\Component\Serializer\NameConverter\NameConverterInterface; 6 | 7 | class CheeseSearchFilter extends AbstractFilter 8 | { 9 | - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) 10 | + public function __construct(ManagerRegistry $managerRegistry, NameConverterInterface $nameConverter = null) 11 | { 12 | - parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter); 13 | + parent::__construct($managerRegistry, null, null, [], $nameConverter); 14 | } 15 | 16 | protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) 17 | -------------------------------------------------------------------------------- /_tuts/extension-assert-404-on-test.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index 5517a7f..4753849 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -114,12 +114,13 @@ class CheeseListingResourceTest extends CustomApiTestCase 6 | $cheeseListing1->setOwner($user); 7 | $cheeseListing1->setPrice(1000); 8 | $cheeseListing1->setDescription('cheese'); 9 | + $cheeseListing1->setIsPublished(false); 10 | 11 | $em = $this->getEntityManager(); 12 | $em->persist($cheeseListing1); 13 | $em->flush(); 14 | 15 | $client->request('GET', '/api/cheeses/'.$cheeseListing1->getId()); 16 | - $this->assertResponseStatusCodeSame(200); 17 | + $this->assertResponseStatusCodeSame(404); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /_tuts/extension-empty-cheeselistingispublishedextension.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ApiPlatform/CheeseListingIsPublishedExtension.php b/src/ApiPlatform/CheeseListingIsPublishedExtension.php 2 | new file mode 100644 3 | index 0000000..09652dc 4 | --- /dev/null 5 | +++ b/src/ApiPlatform/CheeseListingIsPublishedExtension.php 6 | @@ -0,0 +1,15 @@ 7 | +description; 7 | } 8 | 9 | + /** 10 | + * @Groups("cheese_listing:read") 11 | + */ 12 | + public function getShortDescription(): ?string 13 | + { 14 | + if (strlen($this->description) < 40) { 15 | + return $this->description; 16 | + } 17 | + 18 | + return substr($this->description, 0, 40).'...'; 19 | + } 20 | + 21 | public function setDescription(string $description): self 22 | { 23 | $this->description = $description; 24 | -------------------------------------------------------------------------------- /_tuts/filter-add-missing-use.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index fd26587..293e3b7 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -4,6 +4,7 @@ namespace App\Entity; 6 | 7 | use ApiPlatform\Core\Annotation\ApiFilter; 8 | use ApiPlatform\Core\Annotation\ApiResource; 9 | +use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter; 10 | use Carbon\Carbon; 11 | use Doctrine\ORM\Mapping as ORM; 12 | use Symfony\Component\Serializer\Annotation\Groups; 13 | -------------------------------------------------------------------------------- /_tuts/filter-add-propertyfilter.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index e31365b..0dbf8fb 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -7,6 +7,7 @@ use ApiPlatform\Core\Annotation\ApiResource; 6 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter; 7 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter; 8 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; 9 | +use ApiPlatform\Core\Serializer\Filter\PropertyFilter; 10 | use Carbon\Carbon; 11 | use Doctrine\ORM\Mapping as ORM; 12 | use Symfony\Component\Serializer\Annotation\Groups; 13 | @@ -26,6 +27,7 @@ use Symfony\Component\Serializer\Annotation\SerializedName; 14 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 15 | * @ApiFilter(SearchFilter::class, properties={"title": "partial", "description": "partial"}) 16 | * @ApiFilter(RangeFilter::class, properties={"price"}) 17 | + * @ApiFilter(PropertyFilter::class) 18 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 19 | */ 20 | class CheeseListing 21 | -------------------------------------------------------------------------------- /_tuts/filter-add-rangefilter.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 199f439..7a32ff6 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -5,6 +5,7 @@ namespace App\Entity; 6 | use ApiPlatform\Core\Annotation\ApiFilter; 7 | use ApiPlatform\Core\Annotation\ApiResource; 8 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter; 9 | +use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter; 10 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; 11 | use Carbon\Carbon; 12 | use Doctrine\ORM\Mapping as ORM; 13 | @@ -24,6 +25,7 @@ use Symfony\Component\Serializer\Annotation\SerializedName; 14 | * ) 15 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 16 | * @ApiFilter(SearchFilter::class, properties={"title": "partial", "description": "partial"}) 17 | + * @ApiFilter(RangeFilter::class, properties={"price"}) 18 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 19 | */ 20 | class CheeseListing 21 | -------------------------------------------------------------------------------- /_tuts/filter-add-searchfilter.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 293e3b7..5b96273 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -5,6 +5,7 @@ namespace App\Entity; 6 | use ApiPlatform\Core\Annotation\ApiFilter; 7 | use ApiPlatform\Core\Annotation\ApiResource; 8 | use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter; 9 | +use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; 10 | use Carbon\Carbon; 11 | use Doctrine\ORM\Mapping as ORM; 12 | use Symfony\Component\Serializer\Annotation\Groups; 13 | @@ -22,6 +23,7 @@ use Symfony\Component\Serializer\Annotation\SerializedName; 14 | * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"} 15 | * ) 16 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 17 | + * @ApiFilter(SearchFilter::class, properties={"title": "partial"}) 18 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 19 | */ 20 | class CheeseListing 21 | -------------------------------------------------------------------------------- /_tuts/formats-add-csv-to-resource.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index c8e098c..3958aa5 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -24,7 +24,8 @@ use Symfony\Component\Serializer\Annotation\SerializedName; 6 | * normalizationContext={"groups"={"cheese_listing:read"}, "swagger_definition_name"="Read"}, 7 | * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"}, 8 | * attributes={ 9 | - * "pagination_items_per_page"=10 10 | + * "pagination_items_per_page"=10, 11 | + * "formats"={"jsonld", "json", "html", "jsonhal", "csv"={"text/csv"}} 12 | * } 13 | * ) 14 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 15 | -------------------------------------------------------------------------------- /_tuts/formats-add-hal.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml 2 | index 4f2e862..ae73e69 100644 3 | --- a/config/packages/api_platform.yaml 4 | +++ b/config/packages/api_platform.yaml 5 | @@ -11,3 +11,6 @@ api_platform: 6 | html: 7 | mime_types: 8 | - text/html 9 | + jsonhal: 10 | + mime_types: 11 | + - application/hal+json 12 | -------------------------------------------------------------------------------- /_tuts/formats-paste-formats-config.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml 2 | index e453d1d..4f2e862 100644 3 | --- a/config/packages/api_platform.yaml 4 | +++ b/config/packages/api_platform.yaml 5 | @@ -1,3 +1,13 @@ 6 | api_platform: 7 | mapping: 8 | paths: ['%kernel.project_dir%/src/Entity'] 9 | + formats: 10 | + jsonld: 11 | + mime_types: 12 | + - application/ld+json 13 | + json: 14 | + mime_types: 15 | + - application/json 16 | + html: 17 | + mime_types: 18 | + - text/html 19 | -------------------------------------------------------------------------------- /_tuts/groups-add-denormalization-group.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index a3fbc15..71883c5 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -15,7 +15,8 @@ use Symfony\Component\Serializer\Annotation\Groups; 6 | * "put" 7 | * }, 8 | * shortName="cheeses", 9 | - * normalizationContext={"groups"={"cheese_listing:read"}} 10 | + * normalizationContext={"groups"={"cheese_listing:read"}}, 11 | + * denormalizationContext={"groups"={"cheese_listing:write"}} 12 | * ) 13 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 14 | */ 15 | -------------------------------------------------------------------------------- /_tuts/groups-add-normalization-group.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 90dff0b..652fc22 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -13,7 +13,8 @@ use Doctrine\ORM\Mapping as ORM; 6 | * "get"={}, 7 | * "put" 8 | * }, 9 | - * shortName="cheeses" 10 | + * shortName="cheeses", 11 | + * normalizationContext={"groups"={"cheese_listing:read"}} 12 | * ) 13 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 14 | */ 15 | -------------------------------------------------------------------------------- /_tuts/groups-add-phpdoc-above-method.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 8a8af5f..fa15f37 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -111,6 +111,8 @@ class CheeseListing 6 | } 7 | 8 | /** 9 | + * How long ago in text that this cheese listing was added. 10 | + * 11 | * @Groups("cheese_listing:read") 12 | */ 13 | public function getCreatedAtAgo(): string 14 | -------------------------------------------------------------------------------- /_tuts/groups-add-read-group-to-some-props.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 652fc22..a3fbc15 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -5,6 +5,7 @@ namespace App\Entity; 6 | use ApiPlatform\Core\Annotation\ApiResource; 7 | use Carbon\Carbon; 8 | use Doctrine\ORM\Mapping as ORM; 9 | +use Symfony\Component\Serializer\Annotation\Groups; 10 | 11 | /** 12 | * @ApiResource( 13 | @@ -29,11 +30,13 @@ class CheeseListing 14 | 15 | /** 16 | * @ORM\Column(type="string", length=255) 17 | + * @Groups({"cheese_listing:read"}) 18 | */ 19 | private $title; 20 | 21 | /** 22 | * @ORM\Column(type="text") 23 | + * @Groups({"cheese_listing:read"}) 24 | */ 25 | private $description; 26 | 27 | @@ -41,6 +44,7 @@ class CheeseListing 28 | * The price of this delicious cheese, in cents 29 | * 30 | * @ORM\Column(type="integer") 31 | + * @Groups({"cheese_listing:read"}) 32 | */ 33 | private $price; 34 | 35 | -------------------------------------------------------------------------------- /_tuts/groups-add-read-to-getcreatedatago.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index f799f5d..8a8af5f 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -110,6 +110,9 @@ class CheeseListing 6 | return $this->createdAt; 7 | } 8 | 9 | + /** 10 | + * @Groups("cheese_listing:read") 11 | + */ 12 | public function getCreatedAtAgo(): string 13 | { 14 | return Carbon::instance($this->getCreatedAt())->diffForHumans(); 15 | -------------------------------------------------------------------------------- /_tuts/groups-add-settextdescription-to-write-group.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index fa15f37..0402c73 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -86,6 +86,11 @@ class CheeseListing 6 | return $this->description; 7 | } 8 | 9 | + /** 10 | + * The description of the cheese as raw text. 11 | + * 12 | + * @Groups("cheese_listing:write") 13 | + */ 14 | public function setTextDescription(string $description): self 15 | { 16 | $this->description = nl2br($description); 17 | -------------------------------------------------------------------------------- /_tuts/groups-add-write-group-to-props.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 71883c5..5d273bb 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -31,7 +31,7 @@ class CheeseListing 6 | 7 | /** 8 | * @ORM\Column(type="string", length=255) 9 | - * @Groups({"cheese_listing:read"}) 10 | + * @Groups({"cheese_listing:read", "cheese_listing:write"}) 11 | */ 12 | private $title; 13 | 14 | @@ -45,7 +45,7 @@ class CheeseListing 15 | * The price of this delicious cheese, in cents 16 | * 17 | * @ORM\Column(type="integer") 18 | - * @Groups({"cheese_listing:read"}) 19 | + * @Groups({"cheese_listing:read", "cheese_listing:write"}) 20 | */ 21 | private $price; 22 | 23 | -------------------------------------------------------------------------------- /_tuts/groups-default-ispublished.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 037d07b..03fed02 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -57,7 +57,7 @@ class CheeseListing 6 | /** 7 | * @ORM\Column(type="boolean") 8 | */ 9 | - private $isPublished; 10 | + private $isPublished = false; 11 | 12 | public function __construct() 13 | { 14 | -------------------------------------------------------------------------------- /_tuts/groups-name-swagger-models.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 5d273bb..f799f5d 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -15,8 +15,8 @@ use Symfony\Component\Serializer\Annotation\Groups; 6 | * "put" 7 | * }, 8 | * shortName="cheeses", 9 | - * normalizationContext={"groups"={"cheese_listing:read"}}, 10 | - * denormalizationContext={"groups"={"cheese_listing:write"}} 11 | + * normalizationContext={"groups"={"cheese_listing:read"}, "swagger_definition_name"="Read"}, 12 | + * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"} 13 | * ) 14 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 15 | */ 16 | -------------------------------------------------------------------------------- /_tuts/groups-re-add-setdescription.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 0402c73..037d07b 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -86,6 +86,13 @@ class CheeseListing 6 | return $this->description; 7 | } 8 | 9 | + public function setDescription(string $description): self 10 | + { 11 | + $this->description = $description; 12 | + 13 | + return $this; 14 | + } 15 | + 16 | /** 17 | * The description of the cheese as raw text. 18 | * 19 | -------------------------------------------------------------------------------- /_tuts/hydra-add-var-description.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index f8a8f1d..8a8ec3d 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -29,6 +29,8 @@ class CheeseListing 6 | private $description; 7 | 8 | /** 9 | + * The price of this delicious cheese, in cents 10 | + * 11 | * @ORM\Column(type="integer") 12 | */ 13 | private $price; 14 | -------------------------------------------------------------------------------- /_tuts/installation-upgrade-doctrine-annotations.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/installation-upgrade-doctrine-annotations.diff -------------------------------------------------------------------------------- /_tuts/ismelistener-add-event-subscriber.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php b/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 2 | new file mode 100644 3 | index 0000000..44dde3c 4 | --- /dev/null 5 | +++ b/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 6 | @@ -0,0 +1,21 @@ 7 | + 'onRequestEvent', 25 | + ]; 26 | + } 27 | +} 28 | -------------------------------------------------------------------------------- /_tuts/ismelistener-dd-data-attributes.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php b/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 2 | index 33a37f3..0a20470 100644 3 | --- a/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 4 | +++ b/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 5 | @@ -18,6 +18,7 @@ class SetIsMeOnCurrentUserSubscriber implements EventSubscriberInterface 6 | 7 | public function onRequestEvent(RequestEvent $event) 8 | { 9 | + dd($event->getRequest()->attributes->get('data')); 10 | if (!$event->isMasterRequest()) { 11 | return; 12 | } 13 | -------------------------------------------------------------------------------- /_tuts/ismelistener-default-isme-to-false.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 064f562..cec41f7 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -98,7 +98,7 @@ class User implements UserInterface 6 | * 7 | * @Groups({"user:read"}) 8 | */ 9 | - private $isMe; 10 | + private $isMe = false; 11 | 12 | public function __construct() 13 | { 14 | @@ -258,10 +258,6 @@ class User implements UserInterface 15 | 16 | public function getIsMe(): bool 17 | { 18 | - if ($this->isMe === null) { 19 | - throw new \LogicException('The isMe field has not been initialized'); 20 | - } 21 | - 22 | return $this->isMe; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /_tuts/ismelistener-remove-dd.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php b/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 2 | index 0a20470..33a37f3 100644 3 | --- a/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 4 | +++ b/src/EventSubscriber/SetIsMeOnCurrentUserSubscriber.php 5 | @@ -18,7 +18,6 @@ class SetIsMeOnCurrentUserSubscriber implements EventSubscriberInterface 6 | 7 | public function onRequestEvent(RequestEvent $event) 8 | { 9 | - dd($event->getRequest()->attributes->get('data')); 10 | if (!$event->isMasterRequest()) { 11 | return; 12 | } 13 | -------------------------------------------------------------------------------- /_tuts/ismvplistener-add-field.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index cec41f7..0233abc 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -100,6 +100,13 @@ class User implements UserInterface 6 | */ 7 | private $isMe = false; 8 | 9 | + /** 10 | + * Returns true if this user is an MVP 11 | + * 12 | + * @Groups({"user:read"}) 13 | + */ 14 | + private $isMvp = false; 15 | + 16 | public function __construct() 17 | { 18 | $this->cheeseListings = new ArrayCollection(); 19 | @@ -265,4 +272,14 @@ class User implements UserInterface 20 | { 21 | $this->isMe = $isMe; 22 | } 23 | + 24 | + public function isMvp(): bool 25 | + { 26 | + return $this->isMvp; 27 | + } 28 | + 29 | + public function setIsMvp(bool $isMvp) 30 | + { 31 | + $this->isMvp = $isMvp; 32 | + } 33 | } 34 | -------------------------------------------------------------------------------- /_tuts/ismvplistener-add-listener.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Doctrine/UserSetIsMvpListener.php b/src/Doctrine/UserSetIsMvpListener.php 2 | new file mode 100644 3 | index 0000000..1b5318a 4 | --- /dev/null 5 | +++ b/src/Doctrine/UserSetIsMvpListener.php 6 | @@ -0,0 +1,13 @@ 7 | +setIsMvp(strpos($user->getUsername(), 'cheese') !== false); 18 | + } 19 | +} 20 | -------------------------------------------------------------------------------- /_tuts/ismvplistener-rename-method.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 0233abc..70fd40f 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -273,7 +273,7 @@ class User implements UserInterface 6 | $this->isMe = $isMe; 7 | } 8 | 9 | - public function isMvp(): bool 10 | + public function getIsMvp(): bool 11 | { 12 | return $this->isMvp; 13 | } 14 | -------------------------------------------------------------------------------- /_tuts/ismvplistener-update-test.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index a8a32d6..89510b9 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -48,7 +48,10 @@ class UserResourceTest extends CustomApiTestCase 6 | public function testGetUser() 7 | { 8 | $client = self::createClient(); 9 | - $user = UserFactory::new()->create(['phoneNumber' => '555.123.4567']); 10 | + $user = UserFactory::new()->create([ 11 | + 'phoneNumber' => '555.123.4567', 12 | + 'username' => 'cheesehead', 13 | + ]); 14 | $authenticatedUser = UserFactory::new()->create(); 15 | $this->logIn($client, $authenticatedUser); 16 | 17 | @@ -56,6 +59,7 @@ class UserResourceTest extends CustomApiTestCase 18 | $this->assertResponseStatusCodeSame(200); 19 | $this->assertJsonContains([ 20 | 'username' => $user->getUsername(), 21 | + 'isMvp' => true, 22 | ]); 23 | 24 | $data = $client->getResponse()->toArray(); 25 | -------------------------------------------------------------------------------- /_tuts/jsonlogin-emitting-vue-authenticated.diff: -------------------------------------------------------------------------------- 1 | diff --git a/assets/js/components/LoginForm.vue b/assets/js/components/LoginForm.vue 2 | index 791c35d..f298d85 100644 3 | --- a/assets/js/components/LoginForm.vue 4 | +++ b/assets/js/components/LoginForm.vue 5 | @@ -45,11 +45,9 @@ 6 | password: this.password 7 | }) 8 | .then(response => { 9 | - console.log(response.headers); 10 | - 11 | - //this.$emit('user-authenticated', userUri); 12 | - //this.email = ''; 13 | - //this.password = ''; 14 | + this.$emit('user-authenticated', response.headers.location); 15 | + this.email = ''; 16 | + this.password = ''; 17 | }).catch(error => { 18 | if (error.response.data.error) { 19 | this.error = error.response.data.error; 20 | -------------------------------------------------------------------------------- /_tuts/jsonlogin-error-on-bad-content-type-header.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php 2 | index 58ed831..fca3d21 100644 3 | --- a/src/Controller/SecurityController.php 4 | +++ b/src/Controller/SecurityController.php 5 | @@ -13,6 +13,12 @@ class SecurityController extends AbstractController 6 | */ 7 | public function login() 8 | { 9 | + if (!$this->isGranted('IS_AUTHENTICATED_FULLY')) { 10 | + return $this->json([ 11 | + 'error' => 'Invalid login request: check that the Content-Type header is "application/json".' 12 | + ], 400); 13 | + } 14 | + 15 | return $this->json([ 16 | 'user' => $this->getUser() ? $this->getUser()->getId() : null] 17 | ); 18 | -------------------------------------------------------------------------------- /_tuts/jsonlogin-fix-content-type.diff: -------------------------------------------------------------------------------- 1 | diff --git a/assets/js/components/LoginForm.vue b/assets/js/components/LoginForm.vue 2 | index 11db5d6..42e7316 100644 3 | --- a/assets/js/components/LoginForm.vue 4 | +++ b/assets/js/components/LoginForm.vue 5 | @@ -43,10 +43,6 @@ 6 | .post('/login', { 7 | email: this.email, 8 | password: this.password 9 | - }, { 10 | - headers: { 11 | - 'content-type': 'application/x-www-form-urlencoded' 12 | - } 13 | }) 14 | .then(response => { 15 | console.log(response.data); 16 | -------------------------------------------------------------------------------- /_tuts/jsonlogin-handle-auth-error.diff: -------------------------------------------------------------------------------- 1 | diff --git a/assets/js/components/LoginForm.vue b/assets/js/components/LoginForm.vue 2 | index e743455..42e7316 100644 3 | --- a/assets/js/components/LoginForm.vue 4 | +++ b/assets/js/components/LoginForm.vue 5 | @@ -51,7 +51,11 @@ 6 | //this.email = ''; 7 | //this.password = ''; 8 | }).catch(error => { 9 | - console.log(error.response.data); 10 | + if (error.response.data.error) { 11 | + this.error = error.response.data.error; 12 | + } else { 13 | + this.error = 'Unknown error'; 14 | + } 15 | }).finally(() => { 16 | this.isLoading = false; 17 | }) 18 | -------------------------------------------------------------------------------- /_tuts/jsonlogin-login-ajax-request.diff: -------------------------------------------------------------------------------- 1 | diff --git a/assets/js/components/LoginForm.vue b/assets/js/components/LoginForm.vue 2 | index ab14df4..e743455 100644 3 | --- a/assets/js/components/LoginForm.vue 4 | +++ b/assets/js/components/LoginForm.vue 5 | @@ -39,7 +39,6 @@ 6 | this.isLoading = true; 7 | this.error = ''; 8 | 9 | - /* 10 | axios 11 | .post('/login', { 12 | email: this.email, 13 | @@ -56,7 +55,6 @@ 14 | }).finally(() => { 15 | this.isLoading = false; 16 | }) 17 | - */ 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /_tuts/jsonlogin-show-bad-content-type-header.diff: -------------------------------------------------------------------------------- 1 | diff --git a/assets/js/components/LoginForm.vue b/assets/js/components/LoginForm.vue 2 | index 42e7316..11db5d6 100644 3 | --- a/assets/js/components/LoginForm.vue 4 | +++ b/assets/js/components/LoginForm.vue 5 | @@ -43,6 +43,10 @@ 6 | .post('/login', { 7 | email: this.email, 8 | password: this.password 9 | + }, { 10 | + headers: { 11 | + 'content-type': 'application/x-www-form-urlencoded' 12 | + } 13 | }) 14 | .then(response => { 15 | console.log(response.data); 16 | -------------------------------------------------------------------------------- /_tuts/logout-add-logout-handler.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/security.yaml b/config/packages/security.yaml 2 | index f8a6e1d..e1a12f3 100644 3 | --- a/config/packages/security.yaml 4 | +++ b/config/packages/security.yaml 5 | @@ -22,6 +22,9 @@ security: 6 | username_path: email 7 | password_path: password 8 | 9 | + logout: 10 | + path: app_logout 11 | + 12 | # activate different ways to authenticate 13 | # https://symfony.com/doc/current/security.html#firewalls-authentication 14 | 15 | diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php 16 | index 7dee470..4482661 100644 17 | --- a/src/Controller/SecurityController.php 18 | +++ b/src/Controller/SecurityController.php 19 | @@ -26,4 +26,12 @@ class SecurityController extends AbstractController 20 | 'Location' => $iriConverter->getIriFromItem($this->getUser()) 21 | ]); 22 | } 23 | + 24 | + /** 25 | + * @Route("/logout", name="app_logout") 26 | + */ 27 | + public function logout() 28 | + { 29 | + throw new \Exception('should not be reached'); 30 | + } 31 | } 32 | -------------------------------------------------------------------------------- /_tuts/normalizer-add-isme-field.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Serializer/Normalizer/UserNormalizer.php b/src/Serializer/Normalizer/UserNormalizer.php 2 | index a56f46d..43520a7 100644 3 | --- a/src/Serializer/Normalizer/UserNormalizer.php 4 | +++ b/src/Serializer/Normalizer/UserNormalizer.php 5 | @@ -27,7 +27,8 @@ class UserNormalizer implements ContextAwareNormalizerInterface, CacheableSuppor 6 | */ 7 | public function normalize($object, $format = null, array $context = array()): array 8 | { 9 | - if ($this->userIsOwner($object)) { 10 | + $isOwner = $this->userIsOwner($object); 11 | + if ($isOwner) { 12 | $context['groups'][] = 'owner:read'; 13 | } 14 | 15 | @@ -35,7 +36,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, CacheableSuppor 16 | 17 | $data = $this->normalizer->normalize($object, $format, $context); 18 | 19 | - // Here: add, edit, or delete some data 20 | + $data['isMe'] = $isOwner; 21 | 22 | return $data; 23 | } 24 | -------------------------------------------------------------------------------- /_tuts/normalizer-fix-phonenumber-test.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index abbb1e0..a802fc4 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -51,7 +51,8 @@ class UserResourceTest extends CustomApiTestCase 6 | public function testGetUser() 7 | { 8 | $client = self::createClient(); 9 | - $user = $this->createUserAndLogIn($client, 'cheeseplease@example.com', 'foo'); 10 | + $user = $this->createUser('cheeseplease@example.com', 'foo'); 11 | + $this->createUserAndLogIn($client, 'authenticated@example.com', 'foo'); 12 | 13 | $user->setPhoneNumber('555.123.4567'); 14 | $em = $this->getEntityManager(); 15 | -------------------------------------------------------------------------------- /_tuts/normalizer-return-false-cacheable.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Serializer/Normalizer/UserNormalizer.php b/src/Serializer/Normalizer/UserNormalizer.php 2 | index 442192d..e2ba9f7 100644 3 | --- a/src/Serializer/Normalizer/UserNormalizer.php 4 | +++ b/src/Serializer/Normalizer/UserNormalizer.php 5 | @@ -49,6 +49,6 @@ class UserNormalizer implements ContextAwareNormalizerInterface, CacheableSuppor 6 | 7 | public function hasCacheableSupportsMethod(): bool 8 | { 9 | - return true; 10 | + return false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /_tuts/operations-configure-shortname.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index a80047b..bde9d6f 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -8,7 +8,8 @@ use Doctrine\ORM\Mapping as ORM; 6 | /** 7 | * @ApiResource( 8 | * collectionOperations={"get", "post"}, 9 | - * itemOperations={"get", "put"} 10 | + * itemOperations={"get", "put"}, 11 | + * shortName="cheeses" 12 | * ) 13 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 14 | */ 15 | -------------------------------------------------------------------------------- /_tuts/operations-configure-to-use-all.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 8a8ec3d..912f9ae 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -6,7 +6,10 @@ use ApiPlatform\Core\Annotation\ApiResource; 6 | use Doctrine\ORM\Mapping as ORM; 7 | 8 | /** 9 | - * @ApiResource() 10 | + * @ApiResource( 11 | + * collectionOperations={"get", "post"}, 12 | + * itemOperations={"get", "put", "delete"} 13 | + * ) 14 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 15 | */ 16 | class CheeseListing 17 | -------------------------------------------------------------------------------- /_tuts/operations-remove-delete-operation.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 912f9ae..a80047b 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping as ORM; 6 | /** 7 | * @ApiResource( 8 | * collectionOperations={"get", "post"}, 9 | - * itemOperations={"get", "put", "delete"} 10 | + * itemOperations={"get", "put"} 11 | * ) 12 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 13 | */ 14 | -------------------------------------------------------------------------------- /_tuts/operations-remove-extra-config.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index c8ac93c..1e4a173 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -9,7 +9,7 @@ use Doctrine\ORM\Mapping as ORM; 6 | * @ApiResource( 7 | * collectionOperations={"get", "post"}, 8 | * itemOperations={ 9 | - * "get"={"path"="/i❤️cheeses/{id}"}, 10 | + * "get"={}, 11 | * "put" 12 | * }, 13 | * shortName="cheeses" 14 | -------------------------------------------------------------------------------- /_tuts/operations-show-expanded-operation.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index bde9d6f..c8ac93c 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -8,7 +8,10 @@ use Doctrine\ORM\Mapping as ORM; 6 | /** 7 | * @ApiResource( 8 | * collectionOperations={"get", "post"}, 9 | - * itemOperations={"get", "put"}, 10 | + * itemOperations={ 11 | + * "get"={"path"="/i❤️cheeses/{id}"}, 12 | + * "put" 13 | + * }, 14 | * shortName="cheeses" 15 | * ) 16 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 17 | -------------------------------------------------------------------------------- /_tuts/pagination-add-pagination-items-per-page.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 0dbf8fb..c8e098c 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -22,7 +22,10 @@ use Symfony\Component\Serializer\Annotation\SerializedName; 6 | * }, 7 | * shortName="cheeses", 8 | * normalizationContext={"groups"={"cheese_listing:read"}, "swagger_definition_name"="Read"}, 9 | - * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"} 10 | + * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"}, 11 | + * attributes={ 12 | + * "pagination_items_per_page"=10 13 | + * } 14 | * ) 15 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 16 | * @ApiFilter(SearchFilter::class, properties={"title": "partial", "description": "partial"}) 17 | -------------------------------------------------------------------------------- /_tuts/pagination-create-dailystatspaginator.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsPaginator.php b/src/DataProvider/DailyStatsPaginator.php 2 | new file mode 100644 3 | index 0000000..c7f7107 4 | --- /dev/null 5 | +++ b/src/DataProvider/DailyStatsPaginator.php 6 | @@ -0,0 +1,28 @@ 7 | +dailyStatsIterator === null) { 25 | + // todo - actually go "load" the stats 26 | + $this->dailyStatsIterator = new \ArrayIterator([]); 27 | + } 28 | + 29 | + return $this->dailyStatsIterator; 30 | + } 31 | } 32 | -------------------------------------------------------------------------------- /_tuts/pagination-pass-in-arguments.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsProvider.php b/src/DataProvider/DailyStatsProvider.php 2 | index 7c0c658..94a407e 100644 3 | --- a/src/DataProvider/DailyStatsProvider.php 4 | +++ b/src/DataProvider/DailyStatsProvider.php 5 | @@ -21,7 +21,11 @@ class DailyStatsProvider implements CollectionDataProviderInterface, ItemDataPro 6 | 7 | public function getCollection(string $resourceClass, string $operationName = null) 8 | { 9 | - return new DailyStatsPaginator($this->statsHelper); 10 | + return new DailyStatsPaginator( 11 | + $this->statsHelper, 12 | + 1, 13 | + 3 14 | + ); 15 | } 16 | 17 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 18 | -------------------------------------------------------------------------------- /_tuts/pagination-return-paginator-object.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsProvider.php b/src/DataProvider/DailyStatsProvider.php 2 | index 5c452c9..62b9990 100644 3 | --- a/src/DataProvider/DailyStatsProvider.php 4 | +++ b/src/DataProvider/DailyStatsProvider.php 5 | @@ -21,7 +21,7 @@ class DailyStatsProvider implements CollectionDataProviderInterface, ItemDataPro 6 | 7 | public function getCollection(string $resourceClass, string $operationName = null) 8 | { 9 | - return $this->statsHelper->fetchMany(); 10 | + return new DailyStatsPaginator(); 11 | } 12 | 13 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 14 | -------------------------------------------------------------------------------- /_tuts/pagination-update-count.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/DailyStatsPaginator.php b/src/DataProvider/DailyStatsPaginator.php 2 | index 52f0848..4bedca0 100644 3 | --- a/src/DataProvider/DailyStatsPaginator.php 4 | +++ b/src/DataProvider/DailyStatsPaginator.php 5 | @@ -30,7 +30,7 @@ class DailyStatsPaginator implements PaginatorInterface, \IteratorAggregate 6 | 7 | public function count() 8 | { 9 | - return 10; 10 | + return iterator_count($this->getIterator()); 11 | } 12 | 13 | public function getIterator() 14 | -------------------------------------------------------------------------------- /_tuts/persisterdecoration-add-service-config.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/services.yaml b/config/services.yaml 2 | index 6c54be5..832bbaf 100644 3 | --- a/config/services.yaml 4 | +++ b/config/services.yaml 5 | @@ -36,3 +36,7 @@ services: 6 | 7 | App\Doctrine\CheeseListingSetOwnerListener: 8 | tags: [doctrine.orm.entity_listener] 9 | + 10 | + App\DataPersister\UserDataPersister: 11 | + bind: 12 | + $decoratedDataPersister: '@api_platform.doctrine.orm.data_persister' 13 | -------------------------------------------------------------------------------- /_tuts/persisterdecoration-comment-out-persist.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/UserDataPersister.php b/src/DataPersister/UserDataPersister.php 2 | index 953e68a..e8a10e1 100644 3 | --- a/src/DataPersister/UserDataPersister.php 4 | +++ b/src/DataPersister/UserDataPersister.php 5 | @@ -35,7 +35,7 @@ class UserDataPersister implements DataPersisterInterface 6 | $data->eraseCredentials(); 7 | } 8 | 9 | - $this->entityManager->persist($data); 10 | + //$this->entityManager->persist($data); 11 | $this->entityManager->flush(); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /_tuts/persisterdecoration-dd-context.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/UserDataPersister.php b/src/DataPersister/UserDataPersister.php 2 | index ae93043..58916fc 100644 3 | --- a/src/DataPersister/UserDataPersister.php 4 | +++ b/src/DataPersister/UserDataPersister.php 5 | @@ -31,6 +31,8 @@ class UserDataPersister implements ContextAwareDataPersisterInterface 6 | */ 7 | public function persist($data, array $context = []) 8 | { 9 | + dump($context); 10 | + 11 | if (!$data->getId()) { 12 | // take any actions needed for a new user 13 | // send registration email 14 | -------------------------------------------------------------------------------- /_tuts/persisterdecoration-re-add.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/UserDataPersister.php b/src/DataPersister/UserDataPersister.php 2 | index e8a10e1..953e68a 100644 3 | --- a/src/DataPersister/UserDataPersister.php 4 | +++ b/src/DataPersister/UserDataPersister.php 5 | @@ -35,7 +35,7 @@ class UserDataPersister implements DataPersisterInterface 6 | $data->eraseCredentials(); 7 | } 8 | 9 | - //$this->entityManager->persist($data); 10 | + $this->entityManager->persist($data); 11 | $this->entityManager->flush(); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /_tuts/persisterdecoration-remove-dd.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/UserDataPersister.php b/src/DataPersister/UserDataPersister.php 2 | index 58916fc..f010e52 100644 3 | --- a/src/DataPersister/UserDataPersister.php 4 | +++ b/src/DataPersister/UserDataPersister.php 5 | @@ -31,7 +31,9 @@ class UserDataPersister implements ContextAwareDataPersisterInterface 6 | */ 7 | public function persist($data, array $context = []) 8 | { 9 | - dump($context); 10 | + if (($context['item_operation_name'] ?? null) === 'put') { 11 | + $this->logger->info(sprintf('User "%s" is being updated!', $data->getId())); 12 | + } 13 | 14 | if (!$data->getId()) { 15 | // take any actions needed for a new user 16 | -------------------------------------------------------------------------------- /_tuts/pre-ep2-using-auto-encoder.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/security.yaml b/config/packages/security.yaml 2 | index e684ee4..fb6441c 100644 3 | --- a/config/packages/security.yaml 4 | +++ b/config/packages/security.yaml 5 | @@ -1,7 +1,7 @@ 6 | security: 7 | encoders: 8 | App\Entity\User: 9 | - algorithm: argon2i 10 | + algorithm: auto 11 | 12 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers 13 | providers: 14 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-add-isme-tests.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index a802fc4..af23a60 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -65,6 +65,9 @@ class UserResourceTest extends CustomApiTestCase 6 | 7 | $data = $client->getResponse()->toArray(); 8 | $this->assertArrayNotHasKey('phoneNumber', $data); 9 | + $this->assertJsonContains([ 10 | + 'isMe' => false, 11 | + ]); 12 | 13 | // refresh the user & elevate 14 | $user = $em->getRepository(User::class)->find($user->getId()); 15 | @@ -74,7 +77,8 @@ class UserResourceTest extends CustomApiTestCase 16 | 17 | $client->request('GET', '/api/users/'.$user->getId()); 18 | $this->assertJsonContains([ 19 | - 'phoneNumber' => '555.123.4567' 20 | + 'phoneNumber' => '555.123.4567', 21 | + 'isMe' => true, 22 | ]); 23 | } 24 | -} 25 | \ No newline at end of file 26 | +} 27 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-annotations-recipe.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/routes/annotations.yaml b/config/routes/annotations.yaml 2 | index d49a502..e92efc5 100644 3 | --- a/config/routes/annotations.yaml 4 | +++ b/config/routes/annotations.yaml 5 | @@ -1,3 +1,7 @@ 6 | controllers: 7 | resource: ../../src/Controller/ 8 | type: annotation 9 | + 10 | +kernel: 11 | + resource: ../../src/Kernel.php 12 | + type: annotation 13 | diff --git a/symfony.lock b/symfony.lock 14 | index b6731db..1362285 100644 15 | --- a/symfony.lock 16 | +++ b/symfony.lock 17 | @@ -22,7 +22,7 @@ 18 | "repo": "github.com/symfony/recipes", 19 | "branch": "master", 20 | "version": "1.0", 21 | - "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672" 22 | + "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" 23 | }, 24 | "files": [ 25 | "config/routes/annotations.yaml" 26 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-disabling-deprecations-in-tests.diff: -------------------------------------------------------------------------------- 1 | diff --git a/phpunit.xml.dist b/phpunit.xml.dist 2 | index ae0173d..ec5fe50 100644 3 | --- a/phpunit.xml.dist 4 | +++ b/phpunit.xml.dist 5 | @@ -13,6 +13,7 @@ 6 | 7 | 8 | 9 | + 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-fixing-small-deprecation.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/bootstrap.php b/config/bootstrap.php 2 | index c66626c..e886610 100644 3 | --- a/config/bootstrap.php 4 | +++ b/config/bootstrap.php 5 | @@ -14,7 +14,7 @@ if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { 6 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 7 | } else { 8 | // load all the .env files 9 | - (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 10 | + (new Dotenv())->loadEnv(dirname(__DIR__).'/.env'); 11 | } 12 | 13 | $_SERVER += $_ENV; 14 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-make-test-encoding-faster.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/test/security.yaml b/config/packages/test/security.yaml 2 | new file mode 100644 3 | index 0000000..5f68df4 4 | --- /dev/null 5 | +++ b/config/packages/test/security.yaml 6 | @@ -0,0 +1,8 @@ 7 | +security: 8 | + encoders: 9 | + App\Entity\User: 10 | + algorithm: auto 11 | + # settings to make hashing very fast on the test environment 12 | + cost: 4 13 | + time_cost: 3 14 | + memory_cost: 10 15 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-remove-using-owner-read.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/_tuts/pre-ep3-remove-using-owner-read.diff -------------------------------------------------------------------------------- /_tuts/pre-ep3-security-recipe.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/security.yaml b/config/packages/security.yaml 2 | index e1a12f3..768f8a8 100644 3 | --- a/config/packages/security.yaml 4 | +++ b/config/packages/security.yaml 5 | @@ -16,6 +16,7 @@ security: 6 | security: false 7 | main: 8 | anonymous: true 9 | + lazy: true 10 | 11 | json_login: 12 | check_path: app_login 13 | diff --git a/symfony.lock b/symfony.lock 14 | index 107143a..aaf9ea7 100644 15 | --- a/symfony.lock 16 | +++ b/symfony.lock 17 | @@ -396,12 +396,12 @@ 18 | ] 19 | }, 20 | "symfony/security-bundle": { 21 | - "version": "3.3", 22 | + "version": "5.1", 23 | "recipe": { 24 | "repo": "github.com/symfony/recipes", 25 | "branch": "master", 26 | - "version": "3.3", 27 | - "ref": "f8a63faa0d9521526499c0a8f403c9964ecb0527" 28 | + "version": "5.1", 29 | + "ref": "0a4bae19389d3b9cba1ca0102e3b2bccea724603" 30 | }, 31 | "files": [ 32 | "config/packages/security.yaml" 33 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-translation-recipe.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml 2 | index e6b1cd6..05a2b3d 100644 3 | --- a/config/packages/translation.yaml 4 | +++ b/config/packages/translation.yaml 5 | @@ -1,6 +1,6 @@ 6 | framework: 7 | - default_locale: '%locale%' 8 | + default_locale: en 9 | translator: 10 | default_path: '%kernel.project_dir%/translations' 11 | fallbacks: 12 | - - '%locale%' 13 | + - en 14 | diff --git a/symfony.lock b/symfony.lock 15 | index aaf9ea7..53f4594 100644 16 | --- a/symfony.lock 17 | +++ b/symfony.lock 18 | @@ -437,7 +437,7 @@ 19 | "repo": "github.com/symfony/recipes", 20 | "branch": "master", 21 | "version": "3.3", 22 | - "ref": "1fb02a6e1c8f3d4232cce485c9afa868d63b115a" 23 | + "ref": "2ad9d2545bce8ca1a863e50e92141f0b9d87ffcd" 24 | }, 25 | "files": [ 26 | "config/packages/translation.yaml", 27 | -------------------------------------------------------------------------------- /_tuts/pre-ep3-unpacking-apip-pack.diff: -------------------------------------------------------------------------------- 1 | diff --git a/symfony.lock b/symfony.lock 2 | index 789ee3c..91eb342 100644 3 | --- a/symfony.lock 4 | +++ b/symfony.lock 5 | @@ -1,7 +1,4 @@ 6 | { 7 | - "api-platform/api-pack": { 8 | - "version": "v1.2.0" 9 | - }, 10 | "api-platform/core": { 11 | "version": "2.1", 12 | "recipe": { 13 | -------------------------------------------------------------------------------- /_tuts/relation-add-orphanremoval.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 5ba7a25..f868e0b 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -57,7 +57,7 @@ class User implements UserInterface 6 | private $username; 7 | 8 | /** 9 | - * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner", cascade={"persist"}) 10 | + * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner", cascade={"persist"}, orphanRemoval=true) 11 | * @Groups({"user:read", "user:write"}) 12 | * @Assert\Valid() 13 | */ 14 | -------------------------------------------------------------------------------- /_tuts/relation-add-valid-annotation.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index e19a82c..5ba7a25 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -59,6 +59,7 @@ class User implements UserInterface 6 | /** 7 | * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner", cascade={"persist"}) 8 | * @Groups({"user:read", "user:write"}) 9 | + * @Assert\Valid() 10 | */ 11 | private $cheeseListings; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/relation-allow-embedded-write.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index e88b803..6dc8609 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -51,7 +51,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="string", length=255, unique=true) 9 | - * @Groups({"user:read", "user:write", "cheese_listing:item:get"}) 10 | + * @Groups({"user:read", "user:write", "cheese_listing:item:get", "cheese_listing:write"}) 11 | * @Assert\NotBlank() 12 | */ 13 | private $username; 14 | -------------------------------------------------------------------------------- /_tuts/relation-embed-cheeselisting-owner.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index b6879ed..cf99d11 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -51,7 +51,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\Column(type="string", length=255, unique=true) 9 | - * @Groups({"user:read", "user:write"}) 10 | + * @Groups({"user:read", "user:write", "cheese_listing:read"}) 11 | * @Assert\NotBlank() 12 | */ 13 | private $username; 14 | -------------------------------------------------------------------------------- /_tuts/relation-embed-user-cheeselistings.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index bbd5f63..779bc69 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -46,7 +46,7 @@ class CheeseListing 6 | 7 | /** 8 | * @ORM\Column(type="string", length=255) 9 | - * @Groups({"cheese_listing:read", "cheese_listing:write"}) 10 | + * @Groups({"cheese_listing:read", "cheese_listing:write", "user:read"}) 11 | * @Assert\NotBlank() 12 | * @Assert\Length( 13 | * min=2, 14 | @@ -67,7 +67,7 @@ class CheeseListing 15 | * The price of this delicious cheese, in cents 16 | * 17 | * @ORM\Column(type="integer") 18 | - * @Groups({"cheese_listing:read", "cheese_listing:write"}) 19 | + * @Groups({"cheese_listing:read", "cheese_listing:write", "user:read"}) 20 | * @Assert\NotBlank() 21 | */ 22 | private $price; 23 | -------------------------------------------------------------------------------- /_tuts/relation-expose-cheeselisting-owner.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index f22c3c0..bbd5f63 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -85,6 +85,7 @@ class CheeseListing 6 | /** 7 | * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="cheeseListings") 8 | * @ORM\JoinColumn(nullable=false) 9 | + * @Groups({"cheese_listing:read", "cheese_listing:write"}) 10 | */ 11 | private $owner; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/relation-expose-user-cheeselistings.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 042652b..b6879ed 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -58,6 +58,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner") 9 | + * @Groups("user:read") 10 | */ 11 | private $cheeseListings; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/relation-fix-update-published-test.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index 7e0bc9c..ede8d29 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -54,6 +54,7 @@ class CheeseListingResourceTest extends CustomApiTestCase 6 | $cheeseListing->setOwner($user1); 7 | $cheeseListing->setPrice(1000); 8 | $cheeseListing->setDescription('mmmm'); 9 | + $cheeseListing->setIsPublished(true); 10 | 11 | $em = $this->getEntityManager(); 12 | $em->persist($cheeseListing); 13 | -------------------------------------------------------------------------------- /_tuts/relation-only-embed-on-get-item.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 779bc69..168ac73 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -18,7 +18,9 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * @ApiResource( 7 | * collectionOperations={"get", "post"}, 8 | * itemOperations={ 9 | - * "get"={}, 10 | + * "get"={ 11 | + * "normalization_context"={"groups"={"cheese_listing:read", "cheese_listing:item:get"}}, 12 | + * }, 13 | * "put" 14 | * }, 15 | * shortName="cheeses", 16 | diff --git a/src/Entity/User.php b/src/Entity/User.php 17 | index cf99d11..e88b803 100644 18 | --- a/src/Entity/User.php 19 | +++ b/src/Entity/User.php 20 | @@ -51,7 +51,7 @@ class User implements UserInterface 21 | 22 | /** 23 | * @ORM\Column(type="string", length=255, unique=true) 24 | - * @Groups({"user:read", "user:write", "cheese_listing:read"}) 25 | + * @Groups({"user:read", "user:write", "cheese_listing:item:get"}) 26 | * @Assert\NotBlank() 27 | */ 28 | private $username; 29 | -------------------------------------------------------------------------------- /_tuts/relation-propertyfilter-on-relation.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index f868e0b..3765742 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -2,7 +2,9 @@ 6 | 7 | namespace App\Entity; 8 | 9 | +use ApiPlatform\Core\Annotation\ApiFilter; 10 | use ApiPlatform\Core\Annotation\ApiResource; 11 | +use ApiPlatform\Core\Serializer\Filter\PropertyFilter; 12 | use Doctrine\Common\Collections\ArrayCollection; 13 | use Doctrine\Common\Collections\Collection; 14 | use Doctrine\ORM\Mapping as ORM; 15 | @@ -16,6 +18,7 @@ use Symfony\Component\Validator\Constraints as Assert; 16 | * normalizationContext={"groups"={"user:read"}}, 17 | * denormalizationContext={"groups"={"user:write"}}, 18 | * ) 19 | + * @ApiFilter(PropertyFilter::class) 20 | * @UniqueEntity(fields={"username"}) 21 | * @UniqueEntity(fields={"email"}) 22 | * @ORM\Entity(repositoryClass="App\Repository\UserRepository") 23 | -------------------------------------------------------------------------------- /_tuts/relation-set-items-on-collection.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 6dc8609..b20233d 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -58,7 +58,7 @@ class User implements UserInterface 6 | 7 | /** 8 | * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner") 9 | - * @Groups("user:read") 10 | + * @Groups({"user:read", "user:write"}) 11 | */ 12 | private $cheeseListings; 13 | 14 | -------------------------------------------------------------------------------- /_tuts/relation-valid-on-cl-owner.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 168ac73..30833a9 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -88,6 +88,7 @@ class CheeseListing 6 | * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="cheeseListings") 7 | * @ORM\JoinColumn(nullable=false) 8 | * @Groups({"cheese_listing:read", "cheese_listing:write"}) 9 | + * @Assert\Valid() 10 | */ 11 | private $owner; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/relationfilter-filter-on-cheeselisting-owner.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 884200e..49f701e 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -34,7 +34,8 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 7 | * @ApiFilter(SearchFilter::class, properties={ 8 | * "title": "partial", 9 | - * "description": "partial" 10 | + * "description": "partial", 11 | + * "owner": "exact" 12 | * }) 13 | * @ApiFilter(RangeFilter::class, properties={"price"}) 14 | * @ApiFilter(PropertyFilter::class) 15 | -------------------------------------------------------------------------------- /_tuts/relationfilter-filter-on-owner-username.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 49f701e..d1f9858 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -35,7 +35,8 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * @ApiFilter(SearchFilter::class, properties={ 7 | * "title": "partial", 8 | * "description": "partial", 9 | - * "owner": "exact" 10 | + * "owner": "exact", 11 | + * "owner.username": "partial" 12 | * }) 13 | * @ApiFilter(RangeFilter::class, properties={"price"}) 14 | * @ApiFilter(PropertyFilter::class) 15 | -------------------------------------------------------------------------------- /_tuts/relationfilter-use-multiple-lines.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index e4f86f1..884200e 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -32,7 +32,10 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * } 7 | * ) 8 | * @ApiFilter(BooleanFilter::class, properties={"isPublished"}) 9 | - * @ApiFilter(SearchFilter::class, properties={"title": "partial", "description": "partial"}) 10 | + * @ApiFilter(SearchFilter::class, properties={ 11 | + * "title": "partial", 12 | + * "description": "partial" 13 | + * }) 14 | * @ApiFilter(RangeFilter::class, properties={"price"}) 15 | * @ApiFilter(PropertyFilter::class) 16 | * @ORM\Entity(repositoryClass="App\Repository\CheeseListingRepository") 17 | -------------------------------------------------------------------------------- /_tuts/security-access-control-for-user.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 3765742..4cdf316 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -15,6 +15,15 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | 7 | /** 8 | * @ApiResource( 9 | + * collectionOperations={ 10 | + * "get"={"access_control"="is_granted('ROLE_USER')"}, 11 | + * "post" 12 | + * }, 13 | + * itemOperations={ 14 | + * "get"={"access_control"="is_granted('ROLE_USER')"}, 15 | + * "put"={"access_control"="is_granted('ROLE_USER') and object == user"}, 16 | + * "delete"={"access_control"="is_granted('ROLE_ADMIN')"} 17 | + * } 18 | * normalizationContext={"groups"={"user:read"}}, 19 | * denormalizationContext={"groups"={"user:write"}}, 20 | * ) 21 | -------------------------------------------------------------------------------- /_tuts/security-only-allow-owners-to-put.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 8636c33..7ec095a 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -20,7 +20,7 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * "get"={ 7 | * "normalization_context"={"groups"={"cheese_listing:read", "cheese_listing:item:get"}}, 8 | * }, 9 | - * "put"={"access_control"="is_granted('ROLE_USER')"}, 10 | + * "put"={"access_control"="is_granted('ROLE_USER') and object.getOwner() == user"}, 11 | * "delete"={"access_control"="is_granted('ROLE_ADMIN')"} 12 | * }, 13 | * collectionOperations={ 14 | -------------------------------------------------------------------------------- /_tuts/security-post-requires-role-user.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 49c990c..47f3fe5 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -24,7 +24,7 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * }, 7 | * collectionOperations={ 8 | * "get", 9 | - * "post" 10 | + * "post"={"access_control"="is_granted('ROLE_USER')"} 11 | * }, 12 | * shortName="cheeses", 13 | * normalizationContext={"groups"={"cheese_listing:read"}, "swagger_definition_name"="Read"}, 14 | -------------------------------------------------------------------------------- /_tuts/security-protect-item-operations.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 47f3fe5..8636c33 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -20,7 +20,8 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * "get"={ 7 | * "normalization_context"={"groups"={"cheese_listing:read", "cheese_listing:item:get"}}, 8 | * }, 9 | - * "put" 10 | + * "put"={"access_control"="is_granted('ROLE_USER')"}, 11 | + * "delete"={"access_control"="is_granted('ROLE_ADMIN')"} 12 | * }, 13 | * collectionOperations={ 14 | * "get", 15 | -------------------------------------------------------------------------------- /_tuts/security-remove-debug-code.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index df1b9b9..938b82f 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -47,7 +47,6 @@ class CheeseListingResourceTest extends CustomApiTestCase 6 | 'json' => ['title' => 'updated'] 7 | ]); 8 | $this->assertResponseStatusCodeSame(403, 'only author can updated'); 9 | - var_dump($client->getResponse()->getContent(true)); 10 | 11 | $this->logIn($client, 'user1@example.com', 'foo'); 12 | $client->request('PUT', '/api/cheeses/'.$cheeseListing->getId(), [ 13 | -------------------------------------------------------------------------------- /_tuts/security-set-collectionoperations.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index d1f9858..49c990c 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -16,13 +16,16 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | 7 | /** 8 | * @ApiResource( 9 | - * collectionOperations={"get", "post"}, 10 | * itemOperations={ 11 | * "get"={ 12 | * "normalization_context"={"groups"={"cheese_listing:read", "cheese_listing:item:get"}}, 13 | * }, 14 | * "put" 15 | * }, 16 | + * collectionOperations={ 17 | + * "get", 18 | + * "post" 19 | + * }, 20 | * shortName="cheeses", 21 | * normalizationContext={"groups"={"cheese_listing:read"}, "swagger_definition_name"="Read"}, 22 | * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"}, 23 | -------------------------------------------------------------------------------- /_tuts/security-show-previous-object.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 222ccc7..62677c0 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -21,7 +21,7 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * "normalization_context"={"groups"={"cheese_listing:read", "cheese_listing:item:get"}}, 7 | * }, 8 | * "put"={ 9 | - * "access_control"="is_granted('ROLE_USER') and object.getOwner() == user", 10 | + * "access_control"="is_granted('ROLE_USER') and previous_object.getOwner() == user", 11 | * "access_control_message"="Only the creator can edit a cheese listing" 12 | * }, 13 | * "delete"={"access_control"="is_granted('ROLE_ADMIN')"} 14 | -------------------------------------------------------------------------------- /_tuts/security-test-for-reassigning-owner.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index 938b82f..c5d5a3e 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -44,7 +44,8 @@ class CheeseListingResourceTest extends CustomApiTestCase 6 | 7 | $this->logIn($client, 'user2@example.com', 'foo'); 8 | $client->request('PUT', '/api/cheeses/'.$cheeseListing->getId(), [ 9 | - 'json' => ['title' => 'updated'] 10 | + // try to trick security by reassigning to this user 11 | + 'json' => ['title' => 'updated', 'owner' => '/api/users/'.$user2->getId()] 12 | ]); 13 | $this->assertResponseStatusCodeSame(403, 'only author can updated'); 14 | 15 | -------------------------------------------------------------------------------- /_tuts/security-use-top-level-access-control.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 4cdf316..d5560e5 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -15,15 +15,16 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | 7 | /** 8 | * @ApiResource( 9 | + * accessControl="is_granted('ROLE_USER')", 10 | * collectionOperations={ 11 | - * "get"={"access_control"="is_granted('ROLE_USER')"}, 12 | - * "post" 13 | + * "get", 14 | + * "post"={"access_control"="is_granted('IS_AUTHENTICATED_ANONYMOUSLY')"}, 15 | * }, 16 | * itemOperations={ 17 | - * "get"={"access_control"="is_granted('ROLE_USER')"}, 18 | + * "get", 19 | * "put"={"access_control"="is_granted('ROLE_USER') and object == user"}, 20 | * "delete"={"access_control"="is_granted('ROLE_ADMIN')"} 21 | - * } 22 | + * }, 23 | * normalizationContext={"groups"={"user:read"}}, 24 | * denormalizationContext={"groups"={"user:write"}}, 25 | * ) 26 | -------------------------------------------------------------------------------- /_tuts/serialization-add-settextdescription.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 1e4a173..1dbe75f 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -81,6 +81,13 @@ class CheeseListing 6 | return $this; 7 | } 8 | 9 | + public function setTextDescription(string $description): self 10 | + { 11 | + $this->description = nl2br($description); 12 | + 13 | + return $this; 14 | + } 15 | + 16 | public function getPrice(): ?int 17 | { 18 | return $this->price; 19 | -------------------------------------------------------------------------------- /_tuts/serialization-getcreatedatago.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 90f9766..90dff0b 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -3,6 +3,7 @@ 6 | namespace App\Entity; 7 | 8 | use ApiPlatform\Core\Annotation\ApiResource; 9 | +use Carbon\Carbon; 10 | use Doctrine\ORM\Mapping as ORM; 11 | 12 | /** 13 | @@ -103,6 +104,11 @@ class CheeseListing 14 | return $this->createdAt; 15 | } 16 | 17 | + public function getCreatedAtAgo(): string 18 | + { 19 | + return Carbon::instance($this->getCreatedAt())->diffForHumans(); 20 | + } 21 | + 22 | public function getIsPublished(): ?bool 23 | { 24 | return $this->isPublished; 25 | -------------------------------------------------------------------------------- /_tuts/serialization-remove-setcreatedat.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 76f7e2d..90f9766 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -52,6 +52,11 @@ class CheeseListing 6 | */ 7 | private $isPublished; 8 | 9 | + public function __construct() 10 | + { 11 | + $this->createdAt = new \DateTimeImmutable(); 12 | + } 13 | + 14 | public function getId(): ?int 15 | { 16 | return $this->id; 17 | @@ -98,13 +103,6 @@ class CheeseListing 18 | return $this->createdAt; 19 | } 20 | 21 | - public function setCreatedAt(\DateTimeInterface $createdAt): self 22 | - { 23 | - $this->createdAt = $createdAt; 24 | - 25 | - return $this; 26 | - } 27 | - 28 | public function getIsPublished(): ?bool 29 | { 30 | return $this->isPublished; 31 | -------------------------------------------------------------------------------- /_tuts/serialization-remove-setdescription.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 1dbe75f..76f7e2d 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -74,13 +74,6 @@ class CheeseListing 6 | return $this->description; 7 | } 8 | 9 | - public function setDescription(string $description): self 10 | - { 11 | - $this->description = $description; 12 | - 13 | - return $this; 14 | - } 15 | - 16 | public function setTextDescription(string $description): self 17 | { 18 | $this->description = nl2br($description); 19 | -------------------------------------------------------------------------------- /_tuts/serializetricks-change-arg-name.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index b04c3de..2fbe732 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -60,9 +60,9 @@ class CheeseListing 6 | */ 7 | private $isPublished = false; 8 | 9 | - public function __construct(string $title) 10 | + public function __construct(string $name) 11 | { 12 | - $this->title = $title; 13 | + $this->title = $name; 14 | $this->createdAt = new \DateTimeImmutable(); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /_tuts/serializetricks-change-back.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 2fbe732..b04c3de 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -60,9 +60,9 @@ class CheeseListing 6 | */ 7 | private $isPublished = false; 8 | 9 | - public function __construct(string $name) 10 | + public function __construct(string $title) 11 | { 12 | - $this->title = $name; 13 | + $this->title = $title; 14 | $this->createdAt = new \DateTimeImmutable(); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /_tuts/serializetricks-constructor.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 32da404..b04c3de 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -60,8 +60,9 @@ class CheeseListing 6 | */ 7 | private $isPublished = false; 8 | 9 | - public function __construct() 10 | + public function __construct(string $title) 11 | { 12 | + $this->title = $title; 13 | $this->createdAt = new \DateTimeImmutable(); 14 | } 15 | 16 | @@ -75,13 +76,6 @@ class CheeseListing 17 | return $this->title; 18 | } 19 | 20 | - public function setTitle(string $title): self 21 | - { 22 | - $this->title = $title; 23 | - 24 | - return $this; 25 | - } 26 | - 27 | public function getDescription(): ?string 28 | { 29 | return $this->description; 30 | -------------------------------------------------------------------------------- /_tuts/serializetricks-make-optional.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index b04c3de..6dc72f1 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -60,7 +60,7 @@ class CheeseListing 6 | */ 7 | private $isPublished = false; 8 | 9 | - public function __construct(string $title) 10 | + public function __construct(string $title = null) 11 | { 12 | $this->title = $title; 13 | $this->createdAt = new \DateTimeImmutable(); 14 | -------------------------------------------------------------------------------- /_tuts/serializetricks-serializedname.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/CheeseListing.php b/src/Entity/CheeseListing.php 2 | index 03fed02..32da404 100644 3 | --- a/src/Entity/CheeseListing.php 4 | +++ b/src/Entity/CheeseListing.php 5 | @@ -6,6 +6,7 @@ use ApiPlatform\Core\Annotation\ApiResource; 6 | use Carbon\Carbon; 7 | use Doctrine\ORM\Mapping as ORM; 8 | use Symfony\Component\Serializer\Annotation\Groups; 9 | +use Symfony\Component\Serializer\Annotation\SerializedName; 10 | 11 | /** 12 | * @ApiResource( 13 | @@ -97,6 +98,7 @@ class CheeseListing 14 | * The description of the cheese as raw text. 15 | * 16 | * @Groups("cheese_listing:write") 17 | + * @SerializedName("description") 18 | */ 19 | public function setTextDescription(string $description): self 20 | { 21 | -------------------------------------------------------------------------------- /_tuts/statechangeaction-add-phpdoc.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/CheeseListingDataPersister.php b/src/DataPersister/CheeseListingDataPersister.php 2 | index 6c04f1b..c5f7e38 100644 3 | --- a/src/DataPersister/CheeseListingDataPersister.php 4 | +++ b/src/DataPersister/CheeseListingDataPersister.php 5 | @@ -19,6 +19,9 @@ class CheeseListingDataPersister implements DataPersisterInterface 6 | return $data instanceof CheeseListing; 7 | } 8 | 9 | + /** 10 | + * @param CheeseListing $data 11 | + */ 12 | public function persist($data) 13 | { 14 | return $this->decoratedDataPersister->persist($data); 15 | -------------------------------------------------------------------------------- /_tuts/statechangeaction-call-decorated.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/CheeseListingDataPersister.php b/src/DataPersister/CheeseListingDataPersister.php 2 | index 7362e7c..6c04f1b 100644 3 | --- a/src/DataPersister/CheeseListingDataPersister.php 4 | +++ b/src/DataPersister/CheeseListingDataPersister.php 5 | @@ -3,6 +3,7 @@ 6 | namespace App\DataPersister; 7 | 8 | use ApiPlatform\Core\DataPersister\DataPersisterInterface; 9 | +use App\Entity\CheeseListing; 10 | 11 | class CheeseListingDataPersister implements DataPersisterInterface 12 | { 13 | @@ -15,13 +16,16 @@ class CheeseListingDataPersister implements DataPersisterInterface 14 | 15 | public function supports($data): bool 16 | { 17 | + return $data instanceof CheeseListing; 18 | } 19 | 20 | public function persist($data) 21 | { 22 | + return $this->decoratedDataPersister->persist($data); 23 | } 24 | 25 | public function remove($data) 26 | { 27 | + return $this->decoratedDataPersister->remove($data); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /_tuts/statechangeaction-check-if-ispublished.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/CheeseListingDataPersister.php b/src/DataPersister/CheeseListingDataPersister.php 2 | index c5f7e38..ae2cde9 100644 3 | --- a/src/DataPersister/CheeseListingDataPersister.php 4 | +++ b/src/DataPersister/CheeseListingDataPersister.php 5 | @@ -24,6 +24,10 @@ class CheeseListingDataPersister implements DataPersisterInterface 6 | */ 7 | public function persist($data) 8 | { 9 | + if ($data->getIsPublished()) { 10 | + // hmm, not enough to know that it was JUST published 11 | + } 12 | + 13 | return $this->decoratedDataPersister->persist($data); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /_tuts/statechangeaction-check-originaldata.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/CheeseListingDataPersister.php b/src/DataPersister/CheeseListingDataPersister.php 2 | index bf8c201..311a7c7 100644 3 | --- a/src/DataPersister/CheeseListingDataPersister.php 4 | +++ b/src/DataPersister/CheeseListingDataPersister.php 5 | @@ -29,8 +29,8 @@ class CheeseListingDataPersister implements DataPersisterInterface 6 | public function persist($data) 7 | { 8 | $originalData = $this->entityManager->getUnitOfWork()->getOriginalEntityData($data); 9 | - dump($originalData); 10 | - if ($data->getIsPublished()) { 11 | + $wasAlreadyPublished = ($originalData['isPublished'] ?? false); 12 | + if ($data->getIsPublished() && !$wasAlreadyPublished) { 13 | $notification = new CheeseNotification($data, 'Cheese listing was created!'); 14 | $this->entityManager->persist($notification); 15 | $this->entityManager->flush(); 16 | -------------------------------------------------------------------------------- /_tuts/statechangeaction-create-empty-persister.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataPersister/CheeseListingDataPersister.php b/src/DataPersister/CheeseListingDataPersister.php 2 | new file mode 100644 3 | index 0000000..926aacf 4 | --- /dev/null 5 | +++ b/src/DataPersister/CheeseListingDataPersister.php 6 | @@ -0,0 +1,20 @@ 7 | +create(); 8 | 9 | - $cheeseListing = CheeseListingFactory::new()->create([ 10 | - 'owner' => $user, 11 | - ]); 12 | + $cheeseListing = CheeseListingFactory::new() 13 | + ->withLongDescription() 14 | + ->create(['owner' => $user]); 15 | 16 | $this->logIn($client, $user); 17 | $client->request('PUT', '/api/cheeses/'.$cheeseListing->getId(), [ 18 | -------------------------------------------------------------------------------- /_tuts/statevalidation-add-unpublish-logic.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublishedValidator.php b/src/Validator/ValidIsPublishedValidator.php 2 | index df09382..335227e 100644 3 | --- a/src/Validator/ValidIsPublishedValidator.php 4 | +++ b/src/Validator/ValidIsPublishedValidator.php 5 | @@ -50,5 +50,11 @@ class ValidIsPublishedValidator extends ConstraintValidator 6 | 7 | return; 8 | } 9 | + 10 | + // we are UNpublishing 11 | + if (!$this->security->isGranted('ROLE_ADMIN')) { 12 | + $this->context->buildViolation('Only admin users can unpublish') 13 | + ->addViolation(); 14 | + } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /_tuts/statevalidation-allow-on-class.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublished.php b/src/Validator/ValidIsPublished.php 2 | index 6dcfcd6..9b81101 100644 3 | --- a/src/Validator/ValidIsPublished.php 4 | +++ b/src/Validator/ValidIsPublished.php 5 | @@ -2,10 +2,12 @@ 6 | 7 | namespace App\Validator; 8 | 9 | +use Doctrine\Common\Annotations\Annotation\Target; 10 | use Symfony\Component\Validator\Constraint; 11 | 12 | /** 13 | * @Annotation 14 | + * @Target({"CLASS"}) 15 | */ 16 | class ValidIsPublished extends Constraint 17 | { 18 | @@ -14,4 +16,9 @@ class ValidIsPublished extends Constraint 19 | * Then, use these in your validator class. 20 | */ 21 | public $message = 'The value "{{ value }}" is not valid.'; 22 | + 23 | + public function getTargets() 24 | + { 25 | + return self::CLASS_CONSTRAINT; 26 | + } 27 | } 28 | -------------------------------------------------------------------------------- /_tuts/statevalidation-comment-out-access-denied.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublishedValidator.php b/src/Validator/ValidIsPublishedValidator.php 2 | index 77160e4..560f8fc 100644 3 | --- a/src/Validator/ValidIsPublishedValidator.php 4 | +++ b/src/Validator/ValidIsPublishedValidator.php 5 | @@ -55,7 +55,7 @@ class ValidIsPublishedValidator extends ConstraintValidator 6 | // we are UNpublishing 7 | if (!$this->security->isGranted('ROLE_ADMIN')) { 8 | // you can return a 403 9 | - throw new AccessDeniedException('Only admin users can unpublish'); 10 | + //throw new AccessDeniedException('Only admin users can unpublish'); 11 | 12 | // or a normal validation error 13 | $this->context->buildViolation('Only admin users can unpublish') 14 | -------------------------------------------------------------------------------- /_tuts/statevalidation-do-not-allow-short-description.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublishedValidator.php b/src/Validator/ValidIsPublishedValidator.php 2 | index 2328e6d..bb89cd6 100644 3 | --- a/src/Validator/ValidIsPublishedValidator.php 4 | +++ b/src/Validator/ValidIsPublishedValidator.php 5 | @@ -35,9 +35,16 @@ class ValidIsPublishedValidator extends ConstraintValidator 6 | return; 7 | } 8 | 9 | - // TODO: implement the validation here 10 | - $this->context->buildViolation($constraint->message) 11 | - ->setParameter('{{ value }}', $value) 12 | - ->addViolation(); 13 | + if ($value->getIsPublished()) { 14 | + // we are publishing! 15 | + 16 | + if (strlen($value->getDescription()) < 100) { 17 | + $this->context->buildViolation('Cannot publish: description is too short!') 18 | + ->atPath('description') 19 | + ->addViolation(); 20 | + } 21 | + 22 | + return; 23 | + } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /_tuts/statevalidation-do-nothing-if-no-change.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublishedValidator.php b/src/Validator/ValidIsPublishedValidator.php 2 | index e2b7604..2328e6d 100644 3 | --- a/src/Validator/ValidIsPublishedValidator.php 4 | +++ b/src/Validator/ValidIsPublishedValidator.php 5 | @@ -27,9 +27,11 @@ class ValidIsPublishedValidator extends ConstraintValidator 6 | $originalData = $this->entityManager 7 | ->getUnitOfWork() 8 | ->getOriginalEntityData($value); 9 | - dd($originalData); 10 | 11 | - if (null === $value || '' === $value) { 12 | + $previousIsPublished = ($originalData['isPublished'] ?? false); 13 | + if ($previousIsPublished === $value->getIsPublished()) { 14 | + // isPublished didn't change! 15 | + 16 | return; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /_tuts/statevalidation-dump-in-validator.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublishedValidator.php b/src/Validator/ValidIsPublishedValidator.php 2 | index b54db6c..f580235 100644 3 | --- a/src/Validator/ValidIsPublishedValidator.php 4 | +++ b/src/Validator/ValidIsPublishedValidator.php 5 | @@ -2,6 +2,7 @@ 6 | 7 | namespace App\Validator; 8 | 9 | +use App\Entity\CheeseListing; 10 | use Symfony\Component\Validator\Constraint; 11 | use Symfony\Component\Validator\ConstraintValidator; 12 | 13 | @@ -11,6 +12,12 @@ class ValidIsPublishedValidator extends ConstraintValidator 14 | { 15 | /* @var $constraint \App\Validator\ValidIsPublished */ 16 | 17 | + if (!$value instanceof CheeseListing) { 18 | + throw new \LogicException('Only CheeseListing is supported'); 19 | + } 20 | + 21 | + dd($value); 22 | + 23 | if (null === $value || '' === $value) { 24 | return; 25 | } 26 | -------------------------------------------------------------------------------- /_tuts/statevalidation-show-accessdeniedexception.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Validator/ValidIsPublishedValidator.php b/src/Validator/ValidIsPublishedValidator.php 2 | index 335227e..77160e4 100644 3 | --- a/src/Validator/ValidIsPublishedValidator.php 4 | +++ b/src/Validator/ValidIsPublishedValidator.php 5 | @@ -4,6 +4,7 @@ namespace App\Validator; 6 | 7 | use App\Entity\CheeseListing; 8 | use Doctrine\ORM\EntityManagerInterface; 9 | +use Symfony\Component\Security\Core\Exception\AccessDeniedException; 10 | use Symfony\Component\Security\Core\Security; 11 | use Symfony\Component\Validator\Constraint; 12 | use Symfony\Component\Validator\ConstraintValidator; 13 | @@ -53,6 +54,10 @@ class ValidIsPublishedValidator extends ConstraintValidator 14 | 15 | // we are UNpublishing 16 | if (!$this->security->isGranted('ROLE_ADMIN')) { 17 | + // you can return a 403 18 | + throw new AccessDeniedException('Only admin users can unpublish'); 19 | + 20 | + // or a normal validation error 21 | $this->context->buildViolation('Only admin users can unpublish') 22 | ->addViolation(); 23 | } 24 | -------------------------------------------------------------------------------- /_tuts/subresource-remove-it.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 504316f..3765742 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -4,7 +4,6 @@ namespace App\Entity; 6 | 7 | use ApiPlatform\Core\Annotation\ApiFilter; 8 | use ApiPlatform\Core\Annotation\ApiResource; 9 | -use ApiPlatform\Core\Annotation\ApiSubresource; 10 | use ApiPlatform\Core\Serializer\Filter\PropertyFilter; 11 | use Doctrine\Common\Collections\ArrayCollection; 12 | use Doctrine\Common\Collections\Collection; 13 | @@ -64,7 +63,6 @@ class User implements UserInterface 14 | * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner", cascade={"persist"}, orphanRemoval=true) 15 | * @Groups({"user:read", "user:write"}) 16 | * @Assert\Valid() 17 | - * @ApiSubresource() 18 | */ 19 | private $cheeseListings; 20 | 21 | -------------------------------------------------------------------------------- /_tuts/subresource-user-cheeselistings.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 3765742..504316f 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -4,6 +4,7 @@ namespace App\Entity; 6 | 7 | use ApiPlatform\Core\Annotation\ApiFilter; 8 | use ApiPlatform\Core\Annotation\ApiResource; 9 | +use ApiPlatform\Core\Annotation\ApiSubresource; 10 | use ApiPlatform\Core\Serializer\Filter\PropertyFilter; 11 | use Doctrine\Common\Collections\ArrayCollection; 12 | use Doctrine\Common\Collections\Collection; 13 | @@ -63,6 +64,7 @@ class User implements UserInterface 14 | * @ORM\OneToMany(targetEntity="App\Entity\CheeseListing", mappedBy="owner", cascade={"persist"}, orphanRemoval=true) 15 | * @Groups({"user:read", "user:write"}) 16 | * @Assert\Valid() 17 | + * @ApiSubresource() 18 | */ 19 | private $cheeseListings; 20 | 21 | -------------------------------------------------------------------------------- /_tuts/swagger-disable-docs.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml 2 | index ae73e69..6be222e 100644 3 | --- a/config/packages/api_platform.yaml 4 | +++ b/config/packages/api_platform.yaml 5 | @@ -14,3 +14,5 @@ api_platform: 6 | jsonhal: 7 | mime_types: 8 | - application/hal+json 9 | + 10 | + enable_docs: false 11 | -------------------------------------------------------------------------------- /_tuts/swagger-disable-entrypoint.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml 2 | index 6be222e..21b9e16 100644 3 | --- a/config/packages/api_platform.yaml 4 | +++ b/config/packages/api_platform.yaml 5 | @@ -16,3 +16,4 @@ api_platform: 6 | - application/hal+json 7 | 8 | enable_docs: false 9 | + enable_entrypoint: false 10 | -------------------------------------------------------------------------------- /_tuts/swagger-disable-in-prod.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml 2 | index 21b9e16..ae73e69 100644 3 | --- a/config/packages/api_platform.yaml 4 | +++ b/config/packages/api_platform.yaml 5 | @@ -14,6 +14,3 @@ api_platform: 6 | jsonhal: 7 | mime_types: 8 | - application/hal+json 9 | - 10 | - enable_docs: false 11 | - enable_entrypoint: false 12 | diff --git a/config/packages/prod/api_platform.yaml b/config/packages/prod/api_platform.yaml 13 | new file mode 100644 14 | index 0000000..6de140c 15 | --- /dev/null 16 | +++ b/config/packages/prod/api_platform.yaml 17 | @@ -0,0 +1,3 @@ 18 | +api_platform: 19 | + enable_docs: false 20 | + enable_entrypoint: false 21 | -------------------------------------------------------------------------------- /_tuts/swagger-undo-that.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/packages/prod/api_platform.yaml b/config/packages/prod/api_platform.yaml 2 | index 6de140c..7fa121e 100644 3 | --- a/config/packages/prod/api_platform.yaml 4 | +++ b/config/packages/prod/api_platform.yaml 5 | @@ -1,3 +1,3 @@ 6 | -api_platform: 7 | - enable_docs: false 8 | - enable_entrypoint: false 9 | +#api_platform: 10 | +# enable_docs: false 11 | +# enable_entrypoint: false 12 | -------------------------------------------------------------------------------- /_tuts/test-add-content-type.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index 57860ae..0726445 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -9,8 +9,9 @@ class CheeseListingResourceTest extends ApiTestCase 6 | public function testCreateCheeseListing() 7 | { 8 | $client = self::createClient(); 9 | - 10 | - $client->request('POST', '/api/cheeses'); 11 | + $client->request('POST', '/api/cheeses', [ 12 | + 'headers' => ['Content-Type' => 'application/json'] 13 | + ]); 14 | $this->assertResponseStatusCodeSame(401); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /_tuts/test-add-test-service.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/services_test.yaml b/config/services_test.yaml 2 | new file mode 100644 3 | index 0000000..200a1a6 4 | --- /dev/null 5 | +++ b/config/services_test.yaml 6 | @@ -0,0 +1,7 @@ 7 | +services: 8 | + # added so we can use the new API Platform test tools before 9 | + # they are released. In API Platform 2.5, this won't be needed. 10 | + test.api_platform.client: 11 | + class: App\ApiPlatform\Test\Client 12 | + arguments: ['@test.client'] 13 | + public: true 14 | -------------------------------------------------------------------------------- /_tuts/test-assert-auth-success-with-400.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index adc9cd7..59c8448 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -20,5 +20,11 @@ class CheeseListingResourceTest extends CustomApiTestCase 6 | $this->assertResponseStatusCodeSame(401); 7 | 8 | $this->createUserAndLogIn($client, 'cheeseplease@example.com', 'foo'); 9 | + 10 | + $client->request('POST', '/api/cheeses', [ 11 | + 'headers' => ['Content-Type' => 'application/json'], 12 | + 'json' => [], 13 | + ]); 14 | + $this->assertResponseStatusCodeSame(400); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /_tuts/test-first-simple-test.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | new file mode 100644 3 | index 0000000..e995998 4 | --- /dev/null 5 | +++ b/tests/Functional/CheeseListingResourceTest.php 6 | @@ -0,0 +1,13 @@ 7 | +assertEquals(42, 42); 18 | + } 19 | +} 20 | -------------------------------------------------------------------------------- /_tuts/test-override-database-url.diff: -------------------------------------------------------------------------------- 1 | diff --git a/.env.test b/.env.test 2 | index 24a43c0..cd8a326 100644 3 | --- a/.env.test 4 | +++ b/.env.test 5 | @@ -2,3 +2,4 @@ 6 | KERNEL_CLASS='App\Kernel' 7 | APP_SECRET='$ecretf0rt3st' 8 | SYMFONY_DEPRECATIONS_HELPER=999999 9 | +DATABASE_URL=mysql://root:@127.0.0.1:3306/cheese_whiz_test 10 | -------------------------------------------------------------------------------- /_tuts/test-test-for-401.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index e995998..57860ae 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -8,6 +8,9 @@ class CheeseListingResourceTest extends ApiTestCase 6 | { 7 | public function testCreateCheeseListing() 8 | { 9 | - $this->assertEquals(42, 42); 10 | + $client = self::createClient(); 11 | + 12 | + $client->request('POST', '/api/cheeses'); 13 | + $this->assertResponseStatusCodeSame(401); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /_tuts/test-use-phpunit-8.diff: -------------------------------------------------------------------------------- 1 | diff --git a/phpunit.xml.dist b/phpunit.xml.dist 2 | index 00eda56..d45120b 100644 3 | --- a/phpunit.xml.dist 4 | +++ b/phpunit.xml.dist 5 | @@ -12,7 +12,7 @@ 6 | 7 | 8 | 9 | - 10 | + 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /_tuts/test-use-reloaddatabasetrait.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/CheeseListingResourceTest.php b/tests/Functional/CheeseListingResourceTest.php 2 | index f40e303..8604af5 100644 3 | --- a/tests/Functional/CheeseListingResourceTest.php 4 | +++ b/tests/Functional/CheeseListingResourceTest.php 5 | @@ -4,9 +4,12 @@ namespace App\Tests\Functional; 6 | 7 | use App\ApiPlatform\Test\ApiTestCase; 8 | use App\Entity\User; 9 | +use Hautelook\AliceBundle\PhpUnit\ReloadDatabaseTrait; 10 | 11 | class CheeseListingResourceTest extends ApiTestCase 12 | { 13 | + use ReloadDatabaseTrait; 14 | + 15 | public function testCreateCheeseListing() 16 | { 17 | $client = self::createClient(); 18 | -------------------------------------------------------------------------------- /_tuts/user-fix-getusername.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 0443c89..7d5279f 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -62,7 +62,7 @@ class User implements UserInterface 6 | */ 7 | public function getUsername(): string 8 | { 9 | - return (string) $this->email; 10 | + return (string) $this->username; 11 | } 12 | 13 | /** 14 | -------------------------------------------------------------------------------- /_tuts/user-make-apiresource.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index b8ebb58..d5405b3 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -2,10 +2,12 @@ 6 | 7 | namespace App\Entity; 8 | 9 | +use ApiPlatform\Core\Annotation\ApiResource; 10 | use Doctrine\ORM\Mapping as ORM; 11 | use Symfony\Component\Security\Core\User\UserInterface; 12 | 13 | /** 14 | + * @ApiResource() 15 | * @ORM\Entity(repositoryClass="App\Repository\UserRepository") 16 | */ 17 | class User implements UserInterface 18 | -------------------------------------------------------------------------------- /_tuts/user-make-entity-add-username.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index fe4a565..0443c89 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -33,6 +33,11 @@ class User implements UserInterface 6 | */ 7 | private $password; 8 | 9 | + /** 10 | + * @ORM\Column(type="string", length=255) 11 | + */ 12 | + private $username; 13 | + 14 | public function getId(): ?int 15 | { 16 | return $this->id; 17 | @@ -110,4 +115,11 @@ class User implements UserInterface 18 | // If you store any temporary, sensitive data on the user, clear it here 19 | // $this->plainPassword = null; 20 | } 21 | + 22 | + public function setUsername(string $username): self 23 | + { 24 | + $this->username = $username; 25 | + 26 | + return $this; 27 | + } 28 | } 29 | -------------------------------------------------------------------------------- /_tuts/user-make-username-unique.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 7d5279f..b8ebb58 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -34,7 +34,7 @@ class User implements UserInterface 6 | private $password; 7 | 8 | /** 9 | - * @ORM\Column(type="string", length=255) 10 | + * @ORM\Column(type="string", length=255, unique=true) 11 | */ 12 | private $username; 13 | 14 | -------------------------------------------------------------------------------- /_tuts/userisme-add-isme-field.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 52ef828..5386af0 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -93,6 +93,11 @@ class User implements UserInterface 6 | */ 7 | private $phoneNumber; 8 | 9 | + /** 10 | + * @Groups({"user:read"}) 11 | + */ 12 | + private $isMe; 13 | + 14 | public function __construct() 15 | { 16 | $this->cheeseListings = new ArrayCollection(); 17 | @@ -248,4 +253,14 @@ class User implements UserInterface 18 | 19 | return $this; 20 | } 21 | + 22 | + public function getIsMe(): bool 23 | + { 24 | + return $this->isMe; 25 | + } 26 | + 27 | + public function setIsMe(bool $isMe) 28 | + { 29 | + $this->isMe = $isMe; 30 | + } 31 | } 32 | -------------------------------------------------------------------------------- /_tuts/userisme-add-phpdoc.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 5386af0..6365ea0 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -94,6 +94,8 @@ class User implements UserInterface 6 | private $phoneNumber; 7 | 8 | /** 9 | + * Returns true if this is the currently-authenticated user 10 | + * 11 | * @Groups({"user:read"}) 12 | */ 13 | private $isMe; 14 | -------------------------------------------------------------------------------- /_tuts/userisme-add-response-status-code-check.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index 5557d86..a8a32d6 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -53,6 +53,7 @@ class UserResourceTest extends CustomApiTestCase 6 | $this->logIn($client, $authenticatedUser); 7 | 8 | $client->request('GET', '/api/users/'.$user->getId()); 9 | + $this->assertResponseStatusCodeSame(200); 10 | $this->assertJsonContains([ 11 | 'username' => $user->getUsername(), 12 | ]); 13 | -------------------------------------------------------------------------------- /_tuts/userisme-dump-users.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/UserDataProvider.php b/src/DataProvider/UserDataProvider.php 2 | index 8ed7b9b..8eb0966 100644 3 | --- a/src/DataProvider/UserDataProvider.php 4 | +++ b/src/DataProvider/UserDataProvider.php 5 | @@ -18,7 +18,10 @@ class UserDataProvider implements ContextAwareCollectionDataProviderInterface, R 6 | 7 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []) 8 | { 9 | - return $this->collectionDataProvider->getCollection($resourceClass, $operationName, $context); 10 | + $users = $this->collectionDataProvider->getCollection($resourceClass, $operationName, $context); 11 | + dd($users); 12 | + 13 | + return $users; 14 | } 15 | 16 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 17 | -------------------------------------------------------------------------------- /_tuts/userisme-empty-data-provider.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/UserDataProvider.php b/src/DataProvider/UserDataProvider.php 2 | new file mode 100644 3 | index 0000000..a359c22 4 | --- /dev/null 5 | +++ b/src/DataProvider/UserDataProvider.php 6 | @@ -0,0 +1,17 @@ 7 | +isMe === null) { 10 | + throw new \LogicException('The isMe field has not been initialized'); 11 | + } 12 | + 13 | return $this->isMe; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /_tuts/userisme-fill-in-supports.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/UserDataProvider.php b/src/DataProvider/UserDataProvider.php 2 | index a359c22..3a49c5c 100644 3 | --- a/src/DataProvider/UserDataProvider.php 4 | +++ b/src/DataProvider/UserDataProvider.php 5 | @@ -4,6 +4,7 @@ namespace App\DataProvider; 6 | 7 | use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; 8 | use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; 9 | +use App\Entity\User; 10 | 11 | class UserDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface 12 | { 13 | @@ -13,5 +14,6 @@ class UserDataProvider implements ContextAwareCollectionDataProviderInterface, R 14 | 15 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 16 | { 17 | + return $resourceClass === User::class; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /_tuts/userisme-inject-specific-provider.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/services.yaml b/config/services.yaml 2 | index c13863c..b35af7d 100644 3 | --- a/config/services.yaml 4 | +++ b/config/services.yaml 5 | @@ -44,3 +44,7 @@ services: 6 | App\DataPersister\CheeseListingDataPersister: 7 | bind: 8 | $decoratedDataPersister: '@api_platform.doctrine.orm.data_persister' 9 | + 10 | + App\DataProvider\UserDataProvider: 11 | + bind: 12 | + $collectionDataProvider: '@api_platform.doctrine.orm.default.collection_data_provider' 13 | -------------------------------------------------------------------------------- /_tuts/userisme-populate-isme-in-item.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/UserDataProvider.php b/src/DataProvider/UserDataProvider.php 2 | index 94f740a..d4a4259 100644 3 | --- a/src/DataProvider/UserDataProvider.php 4 | +++ b/src/DataProvider/UserDataProvider.php 5 | @@ -38,7 +38,16 @@ class UserDataProvider implements ContextAwareCollectionDataProviderInterface, D 6 | 7 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 8 | { 9 | - return $this->itemDataProvider->getItem($resourceClass, $id, $operationName, $context); 10 | + /** @var User|null $item */ 11 | + $item = $this->itemDataProvider->getItem($resourceClass, $id, $operationName, $context); 12 | + 13 | + if (!$item) { 14 | + return null; 15 | + } 16 | + 17 | + $item->setIsMe($this->security->getUser() === $item); 18 | + 19 | + return $item; 20 | } 21 | 22 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 23 | -------------------------------------------------------------------------------- /_tuts/userisme-populate-the-field.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/UserDataProvider.php b/src/DataProvider/UserDataProvider.php 2 | index 8eb0966..8e9180a 100644 3 | --- a/src/DataProvider/UserDataProvider.php 4 | +++ b/src/DataProvider/UserDataProvider.php 5 | @@ -18,8 +18,12 @@ class UserDataProvider implements ContextAwareCollectionDataProviderInterface, R 6 | 7 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []) 8 | { 9 | + /** @var User[] $users */ 10 | $users = $this->collectionDataProvider->getCollection($resourceClass, $operationName, $context); 11 | - dd($users); 12 | + 13 | + foreach ($users as $user) { 14 | + $user->setIsMe(true); 15 | + } 16 | 17 | return $users; 18 | } 19 | -------------------------------------------------------------------------------- /_tuts/userisme-remove-in-normalizer.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Serializer/Normalizer/UserNormalizer.php b/src/Serializer/Normalizer/UserNormalizer.php 2 | index 43520a7..902fc95 100644 3 | --- a/src/Serializer/Normalizer/UserNormalizer.php 4 | +++ b/src/Serializer/Normalizer/UserNormalizer.php 5 | @@ -34,11 +34,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, CacheableSuppor 6 | 7 | $context[self::ALREADY_CALLED] = true; 8 | 9 | - $data = $this->normalizer->normalize($object, $format, $context); 10 | - 11 | - $data['isMe'] = $isOwner; 12 | - 13 | - return $data; 14 | + return $this->normalizer->normalize($object, $format, $context); 15 | } 16 | 17 | public function supportsNormalization($data, $format = null, array $context = []) 18 | -------------------------------------------------------------------------------- /_tuts/userisme-return-all-users.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/DataProvider/UserDataProvider.php b/src/DataProvider/UserDataProvider.php 2 | index 3a49c5c..048f6af 100644 3 | --- a/src/DataProvider/UserDataProvider.php 4 | +++ b/src/DataProvider/UserDataProvider.php 5 | @@ -5,11 +5,20 @@ namespace App\DataProvider; 6 | use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; 7 | use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; 8 | use App\Entity\User; 9 | +use App\Repository\UserRepository; 10 | 11 | class UserDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface 12 | { 13 | + private $userRepository; 14 | + 15 | + public function __construct(UserRepository $userRepository) 16 | + { 17 | + $this->userRepository = $userRepository; 18 | + } 19 | + 20 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []) 21 | { 22 | + return $this->userRepository->findAll(); 23 | } 24 | 25 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 26 | -------------------------------------------------------------------------------- /_tuts/uuid-change-identifier.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 1b58b31..ea79463 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -48,11 +48,13 @@ class User implements UserInterface 6 | * @ORM\Id() 7 | * @ORM\GeneratedValue() 8 | * @ORM\Column(type="integer") 9 | + * @ApiProperty(identifier=false) 10 | */ 11 | private $id; 12 | 13 | /** 14 | * @ORM\Column(type="uuid", unique=true) 15 | + * @ApiProperty(identifier=true) 16 | */ 17 | private $uuid; 18 | 19 | -------------------------------------------------------------------------------- /_tuts/uuid-expose-uuid-in-constructor.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index ea79463..4fb2a0f 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -55,6 +55,7 @@ class User implements UserInterface 6 | /** 7 | * @ORM\Column(type="uuid", unique=true) 8 | * @ApiProperty(identifier=true) 9 | + * @Groups({"user:write"}) 10 | */ 11 | private $uuid; 12 | 13 | @@ -119,10 +120,10 @@ class User implements UserInterface 14 | */ 15 | private $isMvp = false; 16 | 17 | - public function __construct() 18 | + public function __construct(UuidInterface $uuid = null) 19 | { 20 | $this->cheeseListings = new ArrayCollection(); 21 | - $this->uuid = Uuid::uuid4(); 22 | + $this->uuid = $uuid ?: Uuid::uuid4(); 23 | } 24 | 25 | public function getId(): ?int 26 | -------------------------------------------------------------------------------- /_tuts/uuid-fetch-user-in-test.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index 89510b9..842353b 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -21,6 +21,9 @@ class UserResourceTest extends CustomApiTestCase 6 | ]); 7 | $this->assertResponseStatusCodeSame(201); 8 | 9 | + $user = UserFactory::repository()->findOneBy(['email' => 'cheeseplease@example.com']); 10 | + $this->assertNotNull($user); 11 | + 12 | $this->logIn($client, 'cheeseplease@example.com', 'brie'); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /_tuts/uuid-initialize-in-the-constructor.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 4c07a81..f8a78f1 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -10,6 +10,7 @@ use App\Doctrine\UserSetIsMvpListener; 6 | use Doctrine\Common\Collections\ArrayCollection; 7 | use Doctrine\Common\Collections\Collection; 8 | use Doctrine\ORM\Mapping as ORM; 9 | +use Ramsey\Uuid\Uuid; 10 | use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 11 | use Symfony\Component\Security\Core\User\UserInterface; 12 | use Symfony\Component\Serializer\Annotation\Groups; 13 | @@ -118,6 +119,7 @@ class User implements UserInterface 14 | public function __construct() 15 | { 16 | $this->cheeseListings = new ArrayCollection(); 17 | + $this->uuid = Uuid::uuid4(); 18 | } 19 | 20 | public function getId(): ?int 21 | -------------------------------------------------------------------------------- /_tuts/uuid-make-migration-safe.diff: -------------------------------------------------------------------------------- 1 | diff --git a/migrations/Version20200909145236.php b/migrations/Version20200909145236.php 2 | index 8f39b3b..5e348fb 100644 3 | --- a/migrations/Version20200909145236.php 4 | +++ b/migrations/Version20200909145236.php 5 | @@ -20,7 +20,8 @@ final class Version20200909145236 extends AbstractMigration 6 | public function up(Schema $schema) : void 7 | { 8 | // this up() migration is auto-generated, please modify it to your needs 9 | - $this->addSql('ALTER TABLE user ADD uuid CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\''); 10 | + $this->addSql('ALTER TABLE user ADD uuid CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\''); 11 | + $this->addSql('UPDATE user SET uuid = UUID()'); 12 | $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649D17F50A6 ON user (uuid)'); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /_tuts/uuid-remove-header.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index 8771a53..3970778 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -43,7 +43,6 @@ class UserResourceTest extends CustomApiTestCase 6 | 'username' => 'cheeseplease', 7 | 'password' => 'brie' 8 | ], 9 | - 'headers' => ['Content-Type' => 'application/ld+json'], 10 | ]); 11 | $this->assertResponseStatusCodeSame(201); 12 | $this->assertJsonContains([ 13 | -------------------------------------------------------------------------------- /_tuts/uuid-rename-to-id.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 4fb2a0f..9cb29e6 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -55,6 +55,7 @@ class User implements UserInterface 6 | /** 7 | * @ORM\Column(type="uuid", unique=true) 8 | * @ApiProperty(identifier=true) 9 | + * @SerializedName("id") 10 | * @Groups({"user:write"}) 11 | */ 12 | private $uuid; 13 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 14 | index 78b71c0..18c326d 100644 15 | --- a/tests/Functional/UserResourceTest.php 16 | +++ b/tests/Functional/UserResourceTest.php 17 | @@ -38,7 +38,7 @@ class UserResourceTest extends CustomApiTestCase 18 | $uuid = Uuid::uuid4(); 19 | $client->request('POST', '/api/users', [ 20 | 'json' => [ 21 | - 'uuid' => $uuid, 22 | + 'id' => $uuid, 23 | 'email' => 'cheeseplease@example.com', 24 | 'username' => 'cheeseplease', 25 | 'password' => 'brie' 26 | -------------------------------------------------------------------------------- /_tuts/uuid-send-json-ld-content-type.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index 18c326d..8771a53 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -42,7 +42,8 @@ class UserResourceTest extends CustomApiTestCase 6 | 'email' => 'cheeseplease@example.com', 7 | 'username' => 'cheeseplease', 8 | 'password' => 'brie' 9 | - ] 10 | + ], 11 | + 'headers' => ['Content-Type' => 'application/ld+json'], 12 | ]); 13 | $this->assertResponseStatusCodeSame(201); 14 | $this->assertJsonContains([ 15 | -------------------------------------------------------------------------------- /_tuts/validation-cast-to-avoid-errors.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingInput.php b/src/Dto/CheeseListingInput.php 2 | index 24d466d..29f871c 100644 3 | --- a/src/Dto/CheeseListingInput.php 4 | +++ b/src/Dto/CheeseListingInput.php 5 | @@ -56,11 +56,11 @@ class CheeseListingInput 6 | public function createOrUpdateEntity(?CheeseListing $cheeseListing): CheeseListing 7 | { 8 | if (!$cheeseListing) { 9 | - $cheeseListing = new CheeseListing($this->title); 10 | + $cheeseListing = new CheeseListing((string) $this->title); 11 | } 12 | 13 | - $cheeseListing->setDescription($this->description); 14 | - $cheeseListing->setPrice($this->price); 15 | + $cheeseListing->setDescription((string) $this->description); 16 | + $cheeseListing->setPrice((int) $this->price); 17 | $cheeseListing->setOwner($this->owner); 18 | $cheeseListing->setIsPublished($this->isPublished); 19 | 20 | -------------------------------------------------------------------------------- /_tuts/validation-undo-casting.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Dto/CheeseListingInput.php b/src/Dto/CheeseListingInput.php 2 | index 29f871c..24d466d 100644 3 | --- a/src/Dto/CheeseListingInput.php 4 | +++ b/src/Dto/CheeseListingInput.php 5 | @@ -56,11 +56,11 @@ class CheeseListingInput 6 | public function createOrUpdateEntity(?CheeseListing $cheeseListing): CheeseListing 7 | { 8 | if (!$cheeseListing) { 9 | - $cheeseListing = new CheeseListing((string) $this->title); 10 | + $cheeseListing = new CheeseListing($this->title); 11 | } 12 | 13 | - $cheeseListing->setDescription((string) $this->description); 14 | - $cheeseListing->setPrice((int) $this->price); 15 | + $cheeseListing->setDescription($this->description); 16 | + $cheeseListing->setPrice($this->price); 17 | $cheeseListing->setOwner($this->owner); 18 | $cheeseListing->setIsPublished($this->isPublished); 19 | 20 | -------------------------------------------------------------------------------- /_tuts/validationgroups-add-create-group.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 5c1177f..2a52020 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -19,7 +19,10 @@ use Symfony\Component\Validator\Constraints as Assert; 6 | * accessControl="is_granted('ROLE_USER')", 7 | * collectionOperations={ 8 | * "get", 9 | - * "post"={"access_control"="is_granted('IS_AUTHENTICATED_ANONYMOUSLY')"}, 10 | + * "post"={ 11 | + * "access_control"="is_granted('IS_AUTHENTICATED_ANONYMOUSLY')", 12 | + * "validation_groups"={"Default", "create"} 13 | + * }, 14 | * }, 15 | * itemOperations={ 16 | * "get", 17 | @@ -79,7 +82,7 @@ class User implements UserInterface 18 | /** 19 | * @Groups("user:write") 20 | * @SerializedName("password") 21 | - * @Assert\NotBlank() 22 | + * @Assert\NotBlank(groups={"create"}) 23 | */ 24 | private $plainPassword; 25 | 26 | -------------------------------------------------------------------------------- /_tuts/validationgroups-add-test-for-update.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php 2 | index eaf88e5..6709331 100644 3 | --- a/tests/Functional/UserResourceTest.php 4 | +++ b/tests/Functional/UserResourceTest.php 5 | @@ -24,4 +24,20 @@ class UserResourceTest extends CustomApiTestCase 6 | 7 | $this->logIn($client, 'cheeseplease@example.com', 'brie'); 8 | } 9 | + 10 | + public function testUpdateUser() 11 | + { 12 | + $client = self::createClient(); 13 | + $user = $this->createUserAndLogIn($client, 'cheeseplease@example.com', 'foo'); 14 | + 15 | + $client->request('PUT', '/api/users/'.$user->getId(), [ 16 | + 'json' => [ 17 | + 'username' => 'newusername' 18 | + ] 19 | + ]); 20 | + $this->assertResponseIsSuccessful(); 21 | + $this->assertJsonContains([ 22 | + 'username' => 'newusername' 23 | + ]); 24 | + } 25 | } 26 | \ No newline at end of file 27 | -------------------------------------------------------------------------------- /_tuts/validationgroups-plainpassword-notblank.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Entity/User.php b/src/Entity/User.php 2 | index 8b0cf8e..5c1177f 100644 3 | --- a/src/Entity/User.php 4 | +++ b/src/Entity/User.php 5 | @@ -79,6 +79,7 @@ class User implements UserInterface 6 | /** 7 | * @Groups("user:write") 8 | * @SerializedName("password") 9 | + * @Assert\NotBlank() 10 | */ 11 | private $plainPassword; 12 | 13 | -------------------------------------------------------------------------------- /_tuts/voter-exception-on-edge-case.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/Security/Voter/CheeseListingVoter.php b/src/Security/Voter/CheeseListingVoter.php 2 | index 01b6005..c1de011 100644 3 | --- a/src/Security/Voter/CheeseListingVoter.php 4 | +++ b/src/Security/Voter/CheeseListingVoter.php 5 | @@ -37,6 +37,6 @@ class CheeseListingVoter extends Voter 6 | return false; 7 | } 8 | 9 | - return false; 10 | + throw new \Exception(sprintf('Unhandled attribute "%s"', $attribute)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /config/bootstrap.php: -------------------------------------------------------------------------------- 1 | =1.2) 9 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { 10 | $_ENV += $env; 11 | } elseif (!class_exists(Dotenv::class)) { 12 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 13 | } else { 14 | // load all the .env files 15 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 16 | } 17 | 18 | $_SERVER += $_ENV; 19 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 20 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 21 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 22 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | ]; 6 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Put the unique name of your app here: the prefix seed 4 | # is used to compute stable namespaces for cache keys. 5 | #prefix_seed: your_vendor_name/app_name 6 | 7 | # The app cache caches to the filesystem by default. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: ~ 20 | -------------------------------------------------------------------------------- /config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | #default_locale: en 4 | #csrf_protection: true 5 | #http_method_override: true 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | # Remove or comment this section to explicitly disable session support. 9 | session: 10 | handler_id: ~ 11 | cookie_secure: auto 12 | cookie_samesite: lax 13 | 14 | #esi: true 15 | #fragments: true 16 | php_errors: 17 | log: true 18 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: ~ 4 | utf8: true 5 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /config/routes.yaml: -------------------------------------------------------------------------------- 1 | #index: 2 | # path: / 3 | # controller: App\Controller\DefaultController::index 4 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | -------------------------------------------------------------------------------- /sfcasts/ep1-basics/metadata.yml: -------------------------------------------------------------------------------- 1 | chapters: 2 | - install 3 | - api-resource 4 | - swagger 5 | - open-api-spec 6 | - json-ld 7 | - hydra 8 | - profiler 9 | - operations 10 | - serializer 11 | - serialization-groups 12 | - serialization-tricks 13 | - filters 14 | - property-filter 15 | - pagination 16 | - formats 17 | - validation 18 | - user-entity 19 | - user-resource 20 | - relations 21 | - relations-iri 22 | - embedded 23 | - embedded-write 24 | - collections-write 25 | - collections-create 26 | - collections-remove-item 27 | - relation-filtering 28 | - subresources 29 | -------------------------------------------------------------------------------- /sfcasts/ep2-security/metadata.yml: -------------------------------------------------------------------------------- 1 | chapters: 2 | - production-docs 3 | - tokens-cookies 4 | - json-login 5 | - auth-errors 6 | - login-success 7 | - login-response 8 | - data-page-load 9 | - samesite-csrf 10 | - access-control 11 | - test-setup 12 | - backport-api-tests 13 | - api-tests 14 | - test-user-login 15 | - test-reset-database 16 | - rock-solid-test-setup 17 | - acl-cheese-owner 18 | - previous-object 19 | - access-control-voter 20 | - plain-password 21 | - encode-user-password 22 | - validation-groups 23 | - conditional-field-setup 24 | - test-roles-reload 25 | - service-decoration 26 | - context-builder 27 | - auto-groups 28 | - resource-metadata-factory 29 | - uncached-metadata 30 | - custom-normalizer 31 | - normalizer-chain 32 | - normalizer-aware 33 | - custom-field 34 | - owner-cleanup 35 | - custom-validator 36 | - validator-logic 37 | - entity-listener 38 | - query-extension 39 | - query-item-extension 40 | - filtered-collection 41 | -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymfonyCasts/api-platform/13e2c8c72489813c889d30fb6fbdbf6f6e114c9c/src/Controller/.gitignore --------------------------------------------------------------------------------