Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Extensions Bundle Laravel Package

dayploy/doctrine-extensions-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup for Laravel (Non-Doctrine ORM)

  1. Install the Bundle (via Composer):
    composer require dayploy/doctrine-extensions-bundle
    
  2. Generate Encryption Key:
    ./bin/console dayploy:doctrine-extensions:generate
    
    Add the generated key to .env:
    DOCTRINE_ENCRYPTION_KEY=your_generated_key_here
    
  3. Configure Bundle (add to config/bundles.php):
    Dayploy\DoctrineExtensionsBundle\DayployDoctrineExtensionsBundle::class => ['all' => true],
    
  4. First Use Case: Encrypt a Sensitive Field
    • Create a Doctrine Entity (or use DBAL for hybrid Laravel apps):
      use Doctrine\ORM\Mapping as ORM;
      use Dayploy\DoctrineExtensionsBundle\Annotation\Cryptable;
      use Doctrine\DBAL\Types\Types;
      
      #[ORM\Entity]
      class User {
          #[Cryptable(
              nonceProperty: 'ssnNonce',
              encryptedProperty: 'ssnEncrypted'
          )]
          private ?string $ssn = null;
      
          #[ORM\Column(type: Types::BINARY, nullable: true)]
          private ?string $ssnEncrypted = null;
      
          #[ORM\Column(type: Types::BINARY, nullable: true)]
          private ?string $ssnNonce = null;
      }
      
    • Laravel Workaround: Use DBAL for encrypted fields:
      use Doctrine\DBAL\Connection;
      
      $connection = new Connection(['url' => 'mysql://user:pass@localhost/db']);
      $connection->getSchemaManager()->createTable('users', function ($table) {
          $table->addColumn('ssn_encrypted', 'binary', ['length' => 255, 'nullable' => true]);
          $table->addColumn('ssn_nonce', 'binary', ['length' => 255, 'nullable' => true]);
      });
      

Implementation Patterns

1. Hybrid Laravel + Doctrine Workflow

  • For Eloquent Models: Use a trait to mirror the bundle’s logic:
    use Illuminate\Support\Facades\Crypt;
    
    trait Encryptable {
        protected static function encrypt($value) {
            $nonce = random_bytes(16);
            $encrypted = openssl_encrypt($value, 'AES-256-CBC', env('DOCTRINE_ENCRYPTION_KEY'), 0, $nonce);
            return ['nonce' => $nonce, 'encrypted' => $encrypted];
        }
    
        protected static function decrypt($nonce, $encrypted) {
            return openssl_decrypt($encrypted, 'AES-256-CBC', env('DOCTRINE_ENCRYPTION_KEY'), 0, $nonce);
        }
    }
    
  • Usage in Model:
    class User extends Model {
        use Encryptable;
    
        protected $ssnEncrypted;
        protected $ssnNonce;
    
        public function setSsnAttribute($value) {
            $data = self::encrypt($value);
            $this->ssnEncrypted = $data['encrypted'];
            $this->ssnNonce = $data['nonce'];
        }
    
        public function getSsnAttribute() {
            return self::decrypt($this->ssnNonce, $this->ssnEncrypted);
        }
    }
    

2. Doctrine Event Listeners

  • Pre-Persist/Update: Automate encryption via Doctrine events:
    use Doctrine\ORM\Event\LifecycleEventArgs;
    use Doctrine\ORM\Events;
    
    $entityManager->getEventManager()->addEventListener(
        Events::prePersist,
        function ($entity, LifecycleEventArgs $args) {
            if ($entity instanceof User && $entity->ssn) {
                $data = self::encrypt($entity->ssn);
                $entity->ssnEncrypted = $data['encrypted'];
                $entity->ssnNonce = $data['nonce'];
                $entity->ssn = null; // Clear plaintext
            }
        }
    );
    

3. Selective Field Encryption

  • Partial Migration: Encrypt only high-risk fields first:
    #[Cryptable(
        nonceProperty: 'creditCardNonce',
        encryptedProperty: 'creditCardEncrypted',
        fields: ['credit_card_number'] // Only encrypt this field
    )]
    
  • Laravel Migration:
    Schema::table('payments', function (Blueprint $table) {
        $table->binary('credit_card_nonce')->nullable()->after('credit_card_number');
        $table->binary('credit_card_encrypted')->nullable()->after('credit_card_nonce');
    });
    

4. Key Management Integration

  • Laravel Config: Store keys in config/doctrine.php:
    'encryption' => [
        'key' => env('DOCTRINE_ENCRYPTION_KEY'),
        'rotation' => [
            'schedule' => '0 0 1 * *', // Monthly rotation
            'backup_key' => env('DOCTRINE_ENCRYPTION_KEY_BACKUP'),
        ],
    ],
    
  • Rotation Script:
    ./bin/console doctrine:query "UPDATE users SET ssn_encrypted = ENCRYPT(ssn_encrypted, :new_key)" --param=new_key:env(DOCTRINE_ENCRYPTION_KEY_NEW)
    

5. Audit Logging

  • Extend Doctrine events to log access:
    $entityManager->getEventManager()->addEventListener(
        Events::postLoad,
        function ($entity, LifecycleEventArgs $args) {
            if ($entity instanceof User && $entity->ssnEncrypted) {
                Log::info('Decrypted SSN for user ' . $entity->id, ['action' => 'ssn_access']);
            }
        }
    );
    

Gotchas and Tips

Pitfalls

  1. Key Management:

    • Gotcha: Hardcoding keys in .env violates 12-factor principles.
      • Fix: Use Laravel’s config or integrate with AWS KMS/HashiCorp Vault.
    • Tip: Store backup keys in a separate encrypted file (e.g., keys/backup.key.enc).
      openssl enc -aes-256-cbc -salt -in keys/backup.key -out keys/backup.key.enc
      
  2. Binary Column Quirks:

    • Gotcha: MySQL BINARY vs. VARBINARY may cause schema conflicts.
      • Fix: Use VARBINARY(255) for flexibility.
    • Tip: Test with large encrypted values (AES-256-CBC outputs ~32 bytes + nonce).
  3. Doctrine Event Conflicts:

    • Gotcha: Multiple listeners on prePersist may override each other.
      • Fix: Use priority in event listeners:
        $eventManager->addEventListener(Events::prePersist, [$listener, 'encrypt'], 100);
        
  4. Performance Bottlenecks:

    • Gotcha: Encryption on every save() can slow down bulk operations.
      • Fix: Batch encrypt/decrypt outside Doctrine events:
        $users = User::whereNull('ssn_encrypted')->get();
        foreach ($users as $user) {
            $data = self::encrypt($user->ssn);
            $user->ssnEncrypted = $data['encrypted'];
            $user->ssnNonce = $data['nonce'];
            $user->save();
        }
        
  5. Data Corruption:

    • Gotcha: Manual DB edits (e.g., via phpMyAdmin) can corrupt BINARY columns.
      • Fix: Use migrations and Doctrine schema tools exclusively.
  6. Laravel Caching Issues:

    • Gotcha: Decrypted values may persist in cache.
      • Fix: Invalidate cache on updates:
        $this->cache()->forget("user_{$this->id}_ssn");
        

Debugging Tips

  1. Decryption Failures:

    • Symptom: openssl_decrypt() returns false.
    • Debug:
      var_dump(
          hex2bin($entity->ssnNonce),
          $entity->ssnEncrypted,
          env('DOCTRINE_ENCRYPTION_KEY')
      );
      
    • Common Causes:
      • Key mismatch (check .env).
      • Corrupted BINARY data (verify with HEX() in SQL).
  2. Event Listener Not Triggering:

    • Debug: Add a dd() in the listener to confirm
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle