Jump to content

NotionCommotion

Members
  • Posts

    2,446
  • Joined

  • Last visited

  • Days Won

    10

Posts posted by NotionCommotion

  1. Really hoping to get my dilemma resolved as I have tried to multiple times and it just stays in limbo. I provided more access control detail per request even though my real question relates to how best to allow a single object to have reciprocating relationships to multiple other objects.  I recognize that no one has any obligation to help, yet many of you have done so and most of what I know is thanks to you.

    If there just isn't a good answer, please also let me know that so I may stop searching.  I am starting to think that ORMs or at least Doctrine too closely couple the DB schema to the object model, and if I wish to use one, I need to compromise on the database schema or other aspects.

    Thanks again

  2. I gave a little thought on why I have concerns about my proposed schema changes (Option 1).  The problem is I have more concerns about the alternate approaches I could think of (Option 2 and Option 3).

    Option 1. Object that has/owns a property references that property instead of the other way around.

    • Orphan PermissionPolicy records can exist if no Projects, Assets, etc reference it.
    • Can't delete a Projects, Assets, etc, and cascade delete the PermissionPolicy record. 
    • Adds complexity to ensure that a given PermissionPolicy is referenced by only a single Project, Asset, etc.
    • Can't switch from OneToOne to ManyToOne by simply removing the unique constraint.
    Project
    - Id (PK)
    - PermissionPolicyId (FK to PermissionPolicy.Id, UNIQUE)
    - Data
    
    Asset
    - Id (PK)
    - PermissionPolicyId (FK to PermissionPolicy.Id, UNIQUE)
    - Data
    
    PermissionPolicy
    - Id (PK)
    - RequiredPermissionData
    
    Member
    - PermissionPolicyId (PK, FK to PermissionPolicy.Id)
    - UserId (PK, FK to User.Id)
    - MemberPermissionData
    
    User
    - Id (PK)
    - GeneralUserPermissionData
    - OtherData


    Option 2.  Unique tables for each entity.

    • I don't care for the duplication.
    • User will have individual collections for each type.
    Project
    - Id (PK)
    - Data
    
    Asset
    - Id (PK)
    - Data
    
    ProjectPermissionPolicy
    - Id (PK)
    - ProjectId (FK to Project.Id, CASCADE DELETE, UNIQUE)
    - RequiredPermissionData
    
    AssetPermissionPolicy
    - Id (PK)
    - AssetId (FK to Asset.Id, CASCADE DELETE, UNIQUE)
    - RequiredPermissionData
    
    ProjectMember
    - ProjectPermissionPolicyId (PK, FK to ProjectPermissionPolicy.Id, CASCADE DELETE)
    - UserId (PK, FK to User.Id)
    - MemberPermissionData
    
    AssetMember
    - AssetPermissionPolicyId (PK, FK to AssetPermissionPolicy.Id, CASCADE DELETE)
    - UserId (PK, FK to User.Id)
    - MemberPermissionData
    
    User
    - Id (PK)
    - GeneralUserPermissionData
    - OtherData


    Option 3.  Extend every entity from AbstractBaseEntity.

    • I have a few entities which do use inheritance (i.e. CommonToAllSpecification and CustomUserDefinedSpecification) and I will need to have CommonToAllSpecification joined even though there is no access policy for it.
    • Later adding access control to an entity will require changing the primary keys.
    AbstractBaseEntity
    - Id (PK)
    
    Project extends AbstractBaseEntity
    - Id (PK)
    - Data
    
    Asset extends AbstractBaseEntity
    - Id (PK)
    - Data
    
    PermissionPolicy
    - Id (PK)
    - AbstractBaseEntityId (FK to AbstractBaseEntity.Id, CASCADE DELETE, UNIQUE)
    - RequiredPermissionData
    
    Member
    - PermissionPolicyId (PK, FK to PermissionPolicy.Id, CASCADE DELETE)
    - UserId (PK, FK to User.Id)
    - MemberPermissionData
    
    User
    - Id (PK)
    - GeneralUserPermissionData
    - OtherData


     

  3. I am not trying to be vague and the roller coaster example is very close.  And while I appreciate your help regarding my approach to access policy, my desire is to determine how best to define the database schema and relate the entity objects.  I have provided specific details below and hope that it doesn't distract from what I thought was a simple question regarding modelling using composition instead of inheritance.

    • I have these entities (Project, Asset, Vendor) which have properties and some (Project, Asset, but not Vendor) also act as a container for documents.
    • There are three types of users: AdminUser, InternalUser, and ExternalUser, and internal and external users will typically have different access policies.
    • Admin users have the capability to create the parent entities and specify on a per-record basis the access policy for the parent entity and for the documents which they contain.
    • The access policy for the parent entity determines ability to read and update with possible values ALL/NONE.
    • The access policy for the documents is common to all documents contained in a given parent entity, determines ability to create, read, update, and delete with possible values ALL/OWNER/NONE where OWNER represents the logged on user being the user who created the document (note that create doesn't support OWNER).
    • Both internal and external users may also join as a member to a given parent entity, and their permission policy maybe be modified for the given entity.

    An example Project is as follows, Asset will look the same, and Vendor will look the same except not have the document permission policies.

    {
        "id": "01GFJTS5W4DQGVRD6FT1P5SQNW",
        "name": "MyProject",
        "type": "project",
        "otherProperties": "bla",
        "internalUserAccessPolicy": {
            "read_parent": "ALL",
            "update_parent": "NONE",
            "create_documents": "ALL",
            "read_documents": "ALL",
            "update_documents": "OWNER",
            "delete_documents": "NONE"
        },
        "externallUserAccessPolicy": {
            "read_parent": "NONE",
            "update_parent": "NONE",
            "create_documents": "NONE",
            "read_documents": "OWNER",
            "update_documents": "OWNER",
            "delete_documents": "NONE"
        },
        "members": [{
                "userId": "01GFJTSKMT2VVZF90X89D33S38",
                "UserPermissionPolicy": {
                    "read_parent": "ALL",
                    "update_parent": "NONE",
                    "create_documents": "ALL",
                    "read_documents": "ALL",
                    "update_documents": "OWNER",
                    "delete_documents": "OWNER"
                }
            }, {
                "userId": "01GFJV47D64FNBRM4YW1HGK49J",
                "UserPermissionPolicy": {
                    "read_parent": "ALL",
                    "update_parent": "NONE",
                    "create_documents": "ALL",
                    "read_documents": "OWNER",
                    "update_documents": "OWNER",
                    "delete_documents": "NONE"
                }
            }
        ],
        "documents": [{
                "documentId": "01GFJVKAC2CT077865KX97TQVH",
                "name": "siteplan.pdf",
                "path": "https://example.com/documents/01GFJV9WV055RZDDYFA4W6MPJC",
                "owner": "01GFJTSKMT2VVZF90X89D33S38"
            }, {
                "documentId": "01GFJV9WV055RZDDYFA4W6MPJC",
                "name": "drawings.pdf",
                "path": "https://example.com/documents/01GFJVKAC2CT077865KX97TQVH",
                "owner": "01GFJTSKMT2VVZF90X89D33S38"
            }
        ]
    }

    The schema is currently as follows:

    AbstractResource
    - id (PK)
    - name
    - type
    - internalUserAccessPolicy (value object)
    - externallUserAccessPolicy (value object)
    - otherProperties
    
    Project extends AbstractResource
    - id (PK, one-to-one FK to AbstractResource.id)
    - otherProperties
    
    Asset extends AbstractResource
    - id (PK, one-to-one FK to AbstractResource.id)
    - otherProperties
    
    User
    - id (PK)
    - name
    
    Member
    - resourceId (PK, many-to-many FK to AbstractResource.id)
    - userId (PK, many-to-many FK to User.id)
    - userAccessPermission (value object)


    I am thinking of changing the schema to the following where Project and Asset will now have an AccessPolicy property:

    ResourceAccessPolicy
    - id (PK)
    - discriminator (ResourceAccessPolicy or DocumentAccessPolicy)
    - internalUserResourceAccessPolicy (value object)
    - externallUserResourcePolicy (value object)
    
    DocumentAccessPolicy extends ResourceAccessPolicy
    - id (PK, one-to-one FK to ResourceAccessPolicy.id)
    - internalUserDocumentAccessPolicy (value object)
    - externallUserDocumentPolicy (value object)
    
    Project
    - id (PK, one-to-one FK to DocumentAccessPolicy.id)
    - discriminatorCopy (FK to ResourceAccessPolicy.className in order to prevent multiple resources from being associated with the same ResourceAccessPolicy)
    - otherProperties
    
    Asset
    - id (PK, one-to-one FK to DocumentAccessPolicy.id)
    - discriminatorCopy (FK to ResourceAccessPolicy.className in order to prevent multiple resources from being associated with the same ResourceAccessPolicy)
    - otherProperties
    
    Vendor
    - id (PK, one-to-one FK to ResourceAccessPolicy.id)
    - discriminatorCopy (FK to ResourceAccessPolicy.className in order to prevent multiple resources from being associated with the same ResourceAccessPolicy)
    - otherProperties
    
    User
    - id (PK)
    - name
    
    Member
    - accessPolicyId (PK, FK to ResourceAccessPolicy.id)
    - userId (PK, FK to User.id)
    - userAccessPermission (value object)

    Maybe it is obvious that I should do it this way, but I've never done it this way and for unknown reasons, have concerns.  While I will gladly accept any access policy specific critique, please also let me know whether there is anything fundamentally wrong with this schema approach and whether I should make any changes.
     

  4. I like the roller coaster analogy!

    Yep, the roller coaster access policy is as follows:

    • Giant Dipper
      • Ride with an adult: 36" tall or 6 years old
      • Ride solo: 48" tall or 13 years old
    • American Eagle
      • Ride with an adult: 32" tall or 9 years old
      • Ride solo: 38" tall or 15 years old
    • The Fury
      • Ride with an adult: 38" tall or 5 years old
      • Ride solo: 42" tall or 16 years old

    And, not just roller coasters, but similar requirements are used for ferris wheels, haunted houses, bumper cars, and carousels which are modelled slightly differently than roller coasters.

    If the user doesn't meet these requirements, they may take a specialized class specifically for a given ride, and based on the grade they received, the requirements are reduced.

    I model it as such, but later, there is some new requirement and I find the inheritance prevents the flexibility to make the required changes.

    So, how should I model it?

    AbstractRide
    - id (PK)
    - minimum_height_with_adult
    - minimum_age_with_adult
    - minimum_height_solo
    - minimum_age_solo
    - discriminator (roller_coaster, ferris_wheel, etc)
    - applicableCommonData
    
    RollerCoaster extends AbstractRide
    - id (PK, FK to AbstractRide.id)
    - applicableData
    
    FerrisWheel extends AbstractRide
    - id (PK, FK to AbstractRide.id)
    - applicableData
    
    HauntedHouse extends AbstractRide...
    
    UserTakesRideClass
    - ride_id (PK, FK to AbstractRide.id)
    - user_id (PK, FK to User.id)
    - grade
    
    User
    - id
    - height
    - age

     

  5. Thank you requinix and maxxd,

    Sorry about the lack of details and misused definitions.  "Resource" is just an entity class and "access control" is the required permission needed to access a particular record.  In addition, there is a ResourceMember object which is associated with a single User and a single resource object which also has required permission data, and this is the part which I am struggling with.  One approach I might take is something like:

    AbstractResourceUnderAccessControl
    - id (PK)
    - discriminator
    - applicableData
    
    ResourceUnderAccessControlOne extends AbstractResourceUnderAccessControl
    - id (PK, FK to resource_under_access_control.id)
    - applicableData
    
    ResourceUnderAccessControlTwo extends AbstractResourceUnderAccessControl
    - id (PK, FK to resource_under_access_control.id)
    - applicableData
    
    ResourceMember
    - resource_under_access_control_id (PK, FK to resource_under_access_control.id)
    - user_id (PK, FK to user.id)
    - applicableData

    But doing it this way paints me into a corner, and would like to consider having each ResourceUnderAccessControl and the associated AccessControl different objects and using composition.  But it just seems wrong having all these independent ResourceUnderAccessControl entities having a one-to-one relationship to a single AccessControl entity.  In addition to making it more complicated to have the database enforce an AccessControl object to only be associated with a single resource, there is also the concern of later putting another resource under access control and having conflicting IDs, however, for this situation, I am using ULIDs and am not worried about it.

    While I very much appreciate your specific recommendations and advise how to best solve my immediate access control need, I also very much want to better figure out this generic concept as I have been struggling with it literally for years (composition-over-inheritance-examples, alternatives-to-entity-inheritance, favor-traits-with-interfaces-over-inheritance).  Is there anything fundamentally wrong with doing it as I showed in my initial post?

  6. I need certain entity classes (i.e. ResourceUnderAccessControOne and ResourceUnderAccessControlTwo) to have more granular access control, and will need to store applicable for each resource on a per-record basis.

    I am using Doctrine, and one way to do so is have ResourceUnderAccessControlOne and ResourceUnderAccessControlTwo extend AccessControl, however, there are certain shortcomings with Doctrine and inheritance.

    As an alternative approach, I am thinking of using a one-to-one between each individual resource class and Acl, however, for no particular reason, I have concerns with this approach.  Should I?  The following provides a bit more context.

    • I do not believe AccessControl will ever need to directly get the resource tied to it, but the resource will need to directly access its associated AccessControl object.
    • For this schema, ResourceUnderAccessControlOne and ResourceUnderAccessControlTwo can share the same AccessControl which goes against business rules, however, either I can have PHP enforce this rule or add a "type" column to all tables and add the appropriate foreign keys. 
    • While "maybe" I shouldn't be implementing access control this way, I would still hope for advise for the stated question.

    Thanks!

    AccessControl
    - id (pk)
    - applicableDataForAccessControl
    
    ResourceUnderAccessControlOne
    - id (pk, FK to AccessControl.id)
    - applicableDataforResourceOne
    
    ResourceUnderAccessControlTwo
    - id (pk, FK to AccessControl.id)
    - applicableDataforResourceTwo

     

  7. 2 hours ago, kicken said:

    It's unclear to me whether you'd modify that or add a new type since your Permission vs PermissionEnum is confusing to me.  I've not used Enums personally yet, so I was unsure if Doctrine had generic support for them or not.  From that bug report, it looks like it does and you might just need to downgrade for a bit and wait for the fix to get released.

    Yeah, the Permission vs PermissionEnum thing kind of confusing to me as well!  Yes, Doctrine has generic support for them.  Will downgrade and wait it out.  Thanks

    PS.  I had tried to throw an __isString() in my enum, but evidently doing so is a no-no.  Guess an __isInt() is a bit too niche and I understand why it doesn't exist.

  8. The following Doctrine method throws error Warning: Object of class App\\Entity\AccessControl\Acl\PermissionEnum could not be converted to int

    namespace Doctrine\DBAL\Types;
    class SmallIntType extends Type implements PhpIntegerMappingType
    {
        ...
    	public function convertToPHPValue($value, AbstractPlatform $platform)
        {
            return $value === null ? null : (int) $value;
        }
        ...
    }

    PermissionEnum is a backed integer enum.

    enum PermissionEnum: int
    {
        case public     = 0b00;
        case owner      = 0b01;
        case restrict   = 0b10;
        case extra      = 0b11; 
    }

    Similar to the __toString() magic method, is there a way to change my PermissionEnum so when type casted as an integer, returns an integer?

    Supplementary info below if of interest

     While outside of the scope of this question, the following added lines of code were added by Doctrine's latest commit and if removed, the issue goes away.  PermissionEnum is used by class Permission which in turn is converted to an integer when saving the the database.

    namespace Doctrine\ORM\Query;
    class SqlWalker implements TreeWalker
    {
        public function walkSelectExpression($selectExpression)
        {
    		...
                        if (! empty($mapping['enumType'])) {
                            $this->rsm->addEnumResult($columnAlias, $mapping['enumType']);
                        }
    		...
    	}
    }

     

    namespace App\Doctrine\Types;
    
    use App\Entity\AccessControl\Acl\Permission;
    use Doctrine\DBAL\Platforms\AbstractPlatform;
    use Doctrine\DBAL\Types\ConversionException;
    use Doctrine\DBAL\Types\Type;
    
    final class PermissionType extends Type
    {
        private const PERMISSION = 'permission';
    
        public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
        {
            return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration);
        }
    
        public function convertToPHPValue($value, AbstractPlatform $platform): mixed
        {
            if (null === $value) {
                return null;
            }
    
            if (\is_int($value)) {
                return Permission::createFromValue($value);
            }
    
            throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['int', 'null']);
        }
    
        public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed
        {
            if (null === $value) {
                return null;
            }
    
            if ($value instanceof Permission) {
                return $value->getValue();
            }
    
            throw ConversionException::conversionFailedInvalidType($value, $this->getName(), [Permission::class, 'null']);
        }
    
        public function requiresSQLCommentHint(AbstractPlatform $platform): bool
        {
            return true;
        }
    
        public function getName(): string
        {
            return self::PERMISSION;
        }
    }

     

    class Permission
    {
        private const READ      = 0b0000000011;
        private const CREATE    = 0b0000001100;
        private const MODIFY    = 0b0000110000;
        private const DELETE    = 0b0011000000;
        private const COWORKER  = 0b0100000000; // Not currently used.
        private const OWNER     = 0b1000000000; // Not currently used.
    
        public function __construct(
            private ?PermissionEnum $read=null,
            private ?PermissionEnum $create=null,
            private ?PermissionEnum $modify=null,
            private ?PermissionEnum $delete=null,
            private ?bool $restrictToOwner=null,
            private ?bool $coworkers=null
        ) {
        }
    
        public function getValue(): ?int
        {
            if($this->hasNullValue()) {
                throw new Exception('Permission has NULL values.');
            }
            $rs = $this->read->value | $this->create->value << 2 | $this->modify->value << 4 | $this->delete->value << 6;
            $rs = $this->coworkers ? $rs | self::COWORKER : $rs & ~self::COWORKER;
    
            return $this->restrictToOwner ? $rs | self::OWNER : $rs & ~self::OWNER;
        }
    }

     

  9. I wish to use this version of ApiPlatformExtension which is the most current on the 3.0 branch. Note that there is no tag associated with it as v3.0.0 is several days older.

    image.thumb.png.68d0ba476149fac4429f6e7ea6052f21.png

    image.png.56a3df557adf8bea8d7039b6c121cbae.png

     

    Now, on packagist, there are several versions available, and based on the 2022-09-20 08:49 UTC date associated with 3.0.x-dev, it appears that is the one I want.  It is my understanding that this branch only looks like a version, but isn't as indicated by the .x in the name.

    image.png.c62a356e183cb9d9b521165da378dc3c.png

    image.thumb.png.a023ea71be7935e209e57233d673b9a1.png

    How should I make my project use the specific file which I stated above I wish to use?  The project is not in production, so while maybe not ideal, I am okay with using an untagged version.  I've tried the following and multiple similar attempts, but only get the previous version of the file.

    {
        "type": "project",
        "license": "proprietary",
        "minimum-stability": "stable",
        "prefer-stable": true,
        "require": {
            "php": ">=8.1",
            "api-platform/core": "3.0.x-dev",
            ...
        }
    }

     

  10. Per the docs:

    Quote

     

    Class constants can be redefined by a child class. As of PHP 8.1.0, class constants cannot be redefined by a child class if it is defined as final.


     

    Okay, guess that makes sense.  But what doesn't make sense (at least to me) is why I am getting the following error.  Can anyone explain why I am getting it?

    final class DocumentExtension implements QueryCollectionExtensionInterface
    {
        final private const EXEMPT_USER_ROLE = 'ROLE_TENANT_ADMIN';
    
        private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): bool
        {
            if ($this->security->isGranted(self::EXEMPT_USER_ROLE)) {
                return false;
            }
        }
    }

     

    Quote

    Fatal error: Private constant App\Doctrine\Extensions\DocumentExtension::EXEMPT_USER_ROLE cannot be final as it is not visible to other classes in /var/www/facdocs/api/src/Doctrine/Extensions/DocumentExtension.php on line 29

     

  11. 23 minutes ago, kicken said:

    I'd probably just do that if it were me.  Regenerating the user on every request isn't really going to be an issue I think, and might be necessary anyway more often than you'd think.

    Agree it will likely not be an issue and even more agree it will not be an issue if only occurring when the database is being changed.

    25 minutes ago, kicken said:

    I think what this error means is that you're trying to persist the App\Security\TokenUser object, but that object does not correspond to any known entity.  Likewise for the other class.

    Figured so much.  Initially thought that if the content that was being saved was identical both times, it would somehow go through, but more I think about it, the more I think unlikely.

    27 minutes ago, kicken said:

    Doing a little more googling, I'm thinking maybe doctrine's reference proxies might be a solution for you.  Since I don't know how things are structured, I can't really provide a good example but whever you were passing a fully hydrated user before, you'd change to just passing a reference, something like:

    $reference=$em->getReference(User::class, $jwt->getUserIdentifier());
    $blamable->setUser($reference);

    I've conceded on not trying to overly optimize, however, I think this is a great solution.  Let me check right now whether it works.

    On face value, it works great.  The only thing I am not sure about is whether some method such as getId() is being invoked on it and it is being populated from the database.  I looked at the SQL log and found that the user table is being queried three times.  Guess if I was really concerned about performance, I wouldn't be using Doctrine.

    Appreciate the help! 

    Quote

    If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. 

     

  12. 1 hour ago, kicken said:

    The BlameableListener only wants the username / identifier.  If you store that in your JWT, then just pass it along.  There's no need to re-generate the full user object just to pass it into the listener.

     

    Thanks kicken!  Was hoping it was going to be that easy, but nothing is ever easy!

    I have a TokenUser object which is my limited user object which is stored in the token.  When passing it to BlameableListener, I get error: 

    The class 'App\Security\TokenUser' was not found in the chain configured namespaces App\Entity, Tbbc\MoneyBundle\Entity, Money

    TokenUser contains a Ulid property, and when passing it to BlameableListener, I get error:

    The class 'Symfony\Component\Uid\Ulid' was not found in the chain configured namespaces App\Entity, Tbbc\MoneyBundle\Entity, Money

    So, then I tried passing BlameableListener the Ulid string value, but get error:

    Blame is reference, user must be an object

    I apply Blamable as an association, so I expect that explains the last error.

        #[ORM\ManyToOne(targetEntity: UserInterface::class)]
        #[ORM\JoinColumn(nullable: false)]
        #[Gedmo\Blameable(on: 'update')]
        protected ?UserInterface $updateBy = null;

    I don't really understand the chain configured namespaces errors.

    My condensed security.yaml is:

    security:
       providers:
            app_logon_user_provider:
                id: App\Security\LogonUserProvider
            jwt:
                lexik_jwt:
                    class: App\Security\TokenUser    
        firewalls:
            main:
                provider: jwt
                jwt: ~
                json_login:
                    provider: app_logon_user_provider
                    check_path: /authentication_token
                    username_path: email
                    password_path: password
                    success_handler: lexik_jwt_authentication.handler.authentication_success
                    failure_handler: lexik_jwt_authentication.handler.authentication_failure

    TokenUser is:

    final class TokenUser implements JWTUserInterface, BasicUserInterface
    {
        public function __construct(private Ulid $id, private OrganizationType $type, private array $roles, private Ulid $organizationId, private ?Ulid $tenantId)
        {
        }
    
        public static function createFromPayload($id, array $payload):self
        {
            return new self(Ulid::fromString($id), OrganizationType::fromName($payload['type']), $payload['roles'], Ulid::fromString($payload['organizationId']), $payload['tenantId']?Ulid::fromString($payload['tenantId']):null);
        }
        
        // Some more methods...
    }

    My  LogonUserProvider::loadUserByIdentifier() queries the DB for the user and if it exists, returns the doctrine User.  I also tried returning TokenUser instead of the Doctrine User, but I recall it also having issues.  Maybe I should be?

    My JWTCreatedListener sets the payload based on the Doctrine User, and the Jwt package takes it from there.

    final class JWTCreatedListener
    {
        public function onJWTCreated(JWTCreatedEvent $event)
        {
            $user = $event->getUser();
            $payload = $event->getData();
    
            $payload['organizationId'] = $user->getOrganization()->toRfc4122();
            $payload['tenantId'] = ($tenant=$user->getTenant())?$tenant->toRfc4122():null;
            $event->setData($payload);
         }
    }

    I also have another AuthenticationSuccessListener which sets some public data (never understood why this needs to be a separate listener, but oh well...).

    final class AuthenticationSuccessListener
    {
        public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $authenticationSuccessEvent): void
        {        
            $user = $authenticationSuccessEvent->getUser();
            if (!$user instanceof UserInterface) {
                return;
            }
            $data = $authenticationSuccessEvent->getData();
            $data['data'] = [
                'id' => $user->getId()->toRfc4122(),
                'firstName' => $user->getFirstName(),
                'lastName' => $user->getLastName(),
                'roles' => $user->getRoles(),
            ];
            $authenticationSuccessEvent->setData($data);
        }
    }

    The only way I have been able to eliminate errors is pass BlameableListener the hydrated Doctrine Users.  Instead of getting this Doctrine User on any POST/PUT/PATCH requests as I suggested I might do in my initial post, I am doing so during the preFlush event which I suppose is a little better.  Still kind of annoys me, and would rather not do so.

    Any advise?  Thanks!

  13. I am using a 3rd party plugin called blamable which sets the createBy and updateBy property when entity which have these properties are changed. While it happens to by Symfony/Doctrine related, I don't think the solution need be based on either.  It is the responsibility of the developer to set the user within the blamable listener and I have seen this be done by setting it upon each request regardless of whether it will be required for the given request.  I happen to be using JWT's where a minimal user entity is stored in the token and used for most requests and I just query the DB and transform to the real user entity where needed.  As such, I don't want to make the DB call for all requests, and instead also listen for entities which contain these properties are created or updated, but listen faster than the blamable listener and set it first.  All great... Until I discover the blamable bundle also considers an entity changed when something is added or removed from a many-to-many relationship to an entity, and my listener doesn't get triggered, and updateBy is set to null resulting in a not-null constraint error.

    I suppose I "could" try to predict these edge cases, but feel this is getting too fragile.  One option is to set it very early for all post/patch/put requests before I know whether it will actually be necessary, but my OCD nature doesn't want to (actually, as I write this, thinking doing so might not be so bad).

    Instead, I would like to set it very early with some "preliminary user object" which gets converted to the real user object only when the blamable plugin actually tries to do something with this preliminary user object.  Below is a very condensed version of BlameableListener and AbstractTrackingListener.  I am questioning whether I am going down a rabbit hole which doesn't come back up.  Think so?  If it might be feasible, ideas where to start? Was thinking my preliminary user object might use overloading so that I know when any of the methods are called, but I don't think doing so will be allowed by an interface.  Maybe I am going about this totally the wrong way.

    class BlameableListener extends AbstractTrackingListener
    {
        protected $user;
    
        // This is the method I am suppose to set before blamable needs it.
        public function setUserValue($user)
        {
            $this->user = $user;
        }
    
        public function getFieldValue($meta, $field, $eventAdapter)
        {
            // Does a little work and returns $this->user.
        }
    
        public function prePersist(EventArgs $args)
        {
            // Checks things out and maybe calls updateField().  This is easy as I can independent listen when an entity is persisted (Doctrine talk as scheduled to be intered into the DB).
        }
    
        public function onFlush(EventArgs $args)
        {
            // Checks things out and maybe calls updateField().  This one is not easy as flush happens after the persist event and I have missed the chance to set the user.
        }
    
        protected function updateField($object, $eventAdapter, $meta, $field)
        {
            // $object is an entity which has a updateBy property which needs to be set with the logged on user
            $property = $meta->getReflectionProperty($field);
            // $property is ReflectionProperty Object ([name] => updateBy [class] => App\Entity\Asset\Asset)
            ...
            $newValue = $this->getFieldValue($meta, $field, $eventAdapter);
            // $newValue should be the user but will be NULL if I haven't set it.
            ...
            $property->setValue($object, $newValue);
        }
    }

     

  14. Is there any slick way of applying a method conditionally like my made up syntax below?  Thanks

     

    $flag = true;
    
    $obj
    ->setName('bob')
    ->setAge(99)
    ->?$flag->setFoo('xxx');	//Only call setFoo if $flag is true.
    
    $obj
    ->setName('bob')
    ->setAge(99)
    ->?$flag->setFoo('xxx'):setBar('yyy');	//Call setFoo if $flag is true, else call setBar
    
    
    public function setName(string $name):self;

     

  15. The following four methods were changed.  The first three seem to be correct but not sure about the last one (hasPhysicalMedia).  Agree?  Best way to fix is just type casting the returned value?  Thanks

     

         public function getPhysicalMedia(): ?PhysicalMedia
         {
    -        return $this->activeMedia?$this->activeMedia->getPhysicalMedia():null;
    +        return $this->activeMedia?->getPhysicalMedia();
         }
    
         public function getMediaType(): ?MediaType
         {
    -        return $this->activeMedia?$this->activeMedia->getMediaType():null;
    +        return $this->activeMedia?->getMediaType();
         }
    
         public function getFilename(): ?string
         {
    -        return $this->activeMedia?$this->activeMedia->getFilename():null;
    +        return $this->activeMedia?->getFilename();
         }
    
         public function hasPhysicalMedia(): bool
         {
    -        return $this->activeMedia && $this->activeMedia->hasPhysicalMedia();
    +        return $this->activeMedia?->hasPhysicalMedia();
         }
    class ActiveMedia
    {
        public function getPhysicalMedia(): PhysicalMedia
        {
            return $this->physicalMedia;
        }
    
        public function getMediaType(): ?MediaType
        {
            return $this->mediaType;
        }
    
        public function getFilename(): ?string
        {
            return $this->filename;
        }
    
        public function hasPhysicalMedia(): bool
        {
            return (bool) $this->physicalMedia;
        }
    }

     

  16. Finally left Centos and gave Ubuntu a try.  Also, changed from apache to nginix. All went much easier than Centos but have one issue.

    I wish to have a non-human user dedicated to each website which PHP will run under and postgresql will use.  I created my user but didn't provide a home directory (useradd -M abtfile).  My configuration is shown below and phpinfo shows abtfile as the user but /home/abtfile as the home.

    I am now thinking I should have created a home for the user should keys or similar be needed for it, and think my options are:

    1. Home directory: /home/abtfile          Host site: /var/www/abtfile/public.   Doesn't seem right.
    2. Home directory: /home/abtfile          Host site: /home/abtfile/public.         Better but not sure.
    3. Home directory: /var/www/abtfile    Host site: /var/www/abtfile/public.    Likely but not sure.

    Questions.

    1. Should abtfile user have a home directory?
    2. Which of my three options or some other approach should be used?
    3. Do I define the home directory location the same way as for any linux user or must it also be defined under some php or nginix config file?

    Thanks!

    PS.  Not having issues (yet), however, if you see any issues under my below configuration files, please let me know.

    /etc/php/8.1/fpm/pool.d/abtfile.conf

    [abtfile]
    user = abtfile
    group = abtfile
    
    ; Call whatever I want. Use ls -l /run/php/ to see existing sockets.
    listen = /var/run/php8.1-fpm-abtfile.sock
    
    ; Must match to the user and group on which NGINX is running
    listen.owner = www-data
    listen.group = www-data
    
    ; Consider changing below valves.
    
    ; mandatory
    pm = dynamic
    pm.max_children = 5 
    pm.min_spare_servers = 1
    pm.max_spare_servers = 3
    
    ; Use default values.
    ; pm.start_servers = 2
    ; pm.max_spawn_rate = 32
    ; pm.process_idle_timeout = 10s
    
    ; Not sure if necessary or correct
    ; Allows to set custom php configuration values.
    ; php_admin_value[disable_functions] = exec,passthru,shell_exec,system
    ; Allows to set PHP boolean flags
    ; php_admin_flag[allow_url_fopen] = off
    
    ; Add environmental data if desired.
    ; env[HOSTNAME] = $HOSTNAME
    ; env[TMP] = /tmp


    /etc/nginx/sites-available/abtfile

    server {
        server_name abtfile.testing.com;
        listen 80;
        listen [::]:80;
    
        root /var/www/abtfile/public;
        index index.php index.html index.htm;
    
        access_log /var/log/nginx/abtfile-access.log;
        error_log /var/log/nginx/abtfile-error.log;
    
        location / {
                try_files $uri $uri/ =404;
        }
    
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/var/run/php8.1-fpm-abtfile.sock;
                fastcgi_split_path_info ^(.+\.php)(/.+)$; # What does this do?
                #fastcgi_index index.php;  # Causes error.  Maybe remove from above?
        }
    }


    /etc/nginx/php_fastcgi.conf 

    try_files $fastcgi_script_name =404;
    include fastcgi_params;
    fastcgi_pass            unix:/run/php/php-fpm.sock;
    fastcgi_index            index.php;
    fastcgi_buffers            8 16k;
    fastcgi_buffer_size        32k;
    fastcgi_hide_header             X-Powered-By;
    fastcgi_hide_header             X-CF-Powered-By;

     

  17. Trying to install a package but received a version error:

    composer require --dev alice
    
    Using version ^2.10 for hautelook/alice-bundle
    ./composer.json has been updated
    Running composer update hautelook/alice-bundle
    Loading composer repositories with package information
    Updating dependencies
    Your requirements could not be resolved to an installable set of packages.
    
      Problem 1
        - Root composer.json requires hautelook/alice-bundle ^2.10 -> satisfiable by hautelook/alice-bundle[2.10.0].
        - hautelook/alice-bundle 2.10.0 requires doctrine/persistence ^2.2 -> found doctrine/persistence[2.2.0, ..., 2.5.3] but the package is fixed to 3.0.2 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    
    Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
    You can also try re-running composer require with an explicit version constraint, e.g. "composer require hautelook/alice-bundle:*" to figure out if any version is installable, or "composer require hautelook/alice-bundle:^2.1" if you know which you need.
    
    Installation failed, reverting ./composer.json and ./composer.lock to their original content.

    An issue has been created and a pull request fixing the issue has been made.  The fix just changed composer.json "doctrine/persistence": "^2.2", to "doctrine/persistence": "^2.2 || ^3.0"

    What are my options to use this package before the github and composer package is updated?

    Ideally, I would like to instruct composer to allow hautelook/alice-bundle to use 3.0 either by adding some argument when I call composer on the command line or by manually editing composer.json as applicable.  Is this possible?

    If this is not possible, I suppose my next best choice is to downgrade doctrine/persistence from 3.02 to 2.2, however, doctrine/persistence is not in my composer.json file and only in my composer.lock file which I suspect I shouldn't be manually editing it.

    Guess my third choice would be to clone it from github, make the changes, and then modify my composer file to use a local copy.

    Maybe there are other approaches?

    Thanks

  18. 19 hours ago, kicken said:

    Yea, It's briefly noted in the documentation for the Iterator class.

    class IteratorIterator implements OuterIterator and this class permits access to methods of the inner iterator via the __call magic method.

    class RecursiveIteratorIterator implements OuterIterator 

    interface OuterIterator extends Iterator

    Should I conclude that any class that implements OuterIterator permits access to methods of the inner iterator via the __call magic method, and not just IteratorIterator?

    Thanks

    PS.  Totally off topic, but is there a way to embed a link when one quotes something (i.e. your quote would include https://www.php.net/manual/en/class.iteratoriterator.php)?

    Array
    (
        [0] => RecursiveIteratorIterator
        [1] => IteratorIterator
        [2] => FilterIterator
        [3] => RecursiveFilterIterator
        [4] => CallbackFilterIterator
        [5] => RecursiveCallbackFilterIterator
        [6] => ParentIterator
        [7] => LimitIterator
        [8] => CachingIterator
        [9] => RecursiveCachingIterator
        [10] => NoRewindIterator
        [11] => AppendIterator
        [12] => InfiniteIterator
        [13] => RegexIterator
        [14] => RecursiveRegexIterator
        [15] => RecursiveTreeIterator
    )

     

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.