doctrine migrations bundle and postgres schema: diff does not work properly - postgresql

I'm using doctrine in my Symfony project, by connecting to an already existent postgres database.
The DB has several schemas, but the symfony app will use nothing but its own schema. The first Entity class I created is the following one:
namespace Belka\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="app_auth.User", schema="app_auth")
*/
class User {
/**
* #ORM\Column(type="string")
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*/
private $username;
/**
* #ORM\Column(type="string")
*/
private $email;
/**
* #ORM\Column(type="string")
*/
private $password;
}
as you can see, the Entity is specifying its own schema app_auth.
Next, I tried to use the migrations bundle. So, I installed and configured it, in order not to consider anything but my schema:
Extract of config.yml:
doctrine:
dbal:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
schema_filter: ~^app_auth\..*~
Extract of config_dev.yml:
doctrine_migrations:
dir_name: "%kernel.root_dir%/../.container/update/DoctrineMigrations"
namespace: Application\Migrations
table_name: "app_auth.migration_versions"
name: Application Migrations
And I run the diff:
php app/console doctrine:migrations:diff
Unfortunately, the migration class generated is the following one:
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20160422171409 extends AbstractMigration
{
/**
* #param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
}
/**
* #param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('CREATE SCHEMA app_auth');
}
}
what's wrong with my configuration then?

Dirty but working solution:
Tested with doctrine/migrations: 3.0.1
We can patch out the schema removing functionality of the DiffGenerator. I personally used this https://github.com/cweagans/composer-patches
Install the package, and then in composer.json "extra" section add:
"patches": {
"doctrine/migrations": {
"Change doctrine behavior to correctly pass PostgreSQL schema to schema_filter while diffing": "patches/doctrine-diff-generator.patch"
}
}
The patch file in patches/doctrine-diff-generator.patch:
--- lib/Doctrine/Migrations/Generator/DiffGenerator.php 2020-06-21 10:55:42.000000000 +0200
+++ lib/Doctrine/Migrations/Generator/DiffGenerator.patched.php 2020-12-23 12:33:02.689405221 +0100
## -142,8 +142,6 ##
*/
private function resolveTableName(string $name) : string
{
- $pos = strpos($name, '.');
-
- return $pos === false ? $name : substr($name, $pos + 1);
+ return $name;
}
}
Of course you have to bare in mind, that updates to doctrine could break the patch. And you are changing this functionality globally for your doctrine, so beware of breaking changes.
Personally, for my use case, which is adding doctrine entities into legacy database, without breaking it, this works nicely, and saves me from adding every new table managed by doctrine into the schema_filter.
Explanation:
When we look into DiffGenerator implementation it actually decodes the table names with schema correctly, but only when collecting the current database schema. This happens in PostgreSqlSchemaManager.php:
/**
* {#inheritdoc}
*/
protected function _getPortableTableDefinition($table)
{
$schemas = $this->getExistingSchemaSearchPaths();
$firstSchema = array_shift($schemas);
if ($table['schema_name'] === $firstSchema) {
return $table['table_name'];
}
return $table['schema_name'] . '.' . $table['table_name'];
}
But then then when calculating the target schema, this information is lost here in DiffGenerator.php:
/**
* Resolve a table name from its fully qualified name. The `$name` argument
* comes from Doctrine\DBAL\Schema\Table#getName which can sometimes return
* a namespaced name with the form `{namespace}.{tableName}`. This extracts
* the table name from that.
*/
private function resolveTableName(string $name) : string
{
$pos = strpos($name, '.');
return $pos === false ? $name : substr($name, $pos + 1);
}
And only afterwards the return value of this function is passed into the schema_filter. Unfortunate, but i guess there was a reason for this :)

Related

TYPO3 DependencyInjection Services.yaml

I switched from TYPO3 V10.4 to V11.5 and had to replace the objectManager->get.
For that I already opend TYPO3 Problem to handle Deprecation #90803.
Due to the fact, that I had more than one objectManager->getto replace, I had to extent to Service.yaml example to
# Configuration/Services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
HGA\Mairlist\:
resource: '../Classes/*'
exclude: '../Classes/Domain/Model/*'
HGA\Mairlist\Utilities\SqlSelect:
public: true
HGA\Mairlist\Utilities\SqlUtility:
public: true
I don't understand the error message and I am also confused about the /\Services.yaml.
Is there something wrong with my Services.yaml file?
Here the part of controller file
**
* #var SqlUtility $sqlUtil
*/
protected $sqlUtil;
public function __construct(SqlUtility $sqlUtil)
{
$this->sqlUtil = $sqlUtil;
}
/**
* #var SqlSelect $sqlSel
*/
protected $sqlSel;
public function __construct(SqlSelect $sqlSel)
{
$this->sqlSel = $sqlSel;
}
Solution:
In Services.yaml, all my code, starting with HGA\, has to be on the same level than _default. That mean, it has to indented one step more (2 spaces).
# Configuration/Services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
HGA\Mairlist\:
resource: '../Classes/*'
exclude: '../Classes/Domain/Model/*'
HGA\Mairlist\Utilities\SqlSelect:
public: true
HGA\Mairlist\Utilities\SqlUtility:
public: true
First of all the constructor has to be changed. If you have more than one injections, you can define them all in the parameters of the constructor. in your case, it would look like this:
use HGA/Mairlist/Utilities/SqlUtil
class yourClass {
**
* #var SqlUtil $sqlUtil
*/
protected $sqlUtil;
**
* #var SqlSelect $sqlSel
*/
protected $sqlUtil;
public function __construct(
SqlUtil $sqlUtil,
SqlSelect $sqlSel
) {
$this->sqlUtil = $sqlUtil;
$this->sqlSel = $sqlSel;
}
}
First try this and tell us if the problem persists.
(btw my edit in the original post awaits approval since a day now)
have you include a namespace in your class? The message say hey found no Namespace "HGA\Mairlist". In Your controller you need
<?php
namespace HGA\Mairlist\Controller;
use HGA\Mairlist\Utilities\SqlUtility;
use HGA\Mairlist\Utilities\SqlSelect;
class yourClass
{
**
* #var SqlUtil $sqlUtil
*/
protected $sqlUtil;
**
* #var SqlSelect $sqlSel
*/
protected $sqlUtil;
public function __construct(SqlUtil $sqlUtil,SqlSelect $sqlSel)
{
$this->sqlUtil = $sqlUtil;
$this->sqlSel = $sqlSel;
}
}
And in the INSTALL TOOL you must then Update the Autoloaddump because your not on composer mode.
INSTALL TOOL - Dump Autoload Information

Unexpected data found during update on eloquent / Laravel

I am using ajax to update a model that contains timestamps, but it throw me an exception:
{message: "Unexpected data found.", exception: "InvalidArgumentException",…}
message: "Unexpected data found."
exception: "InvalidArgumentException"
file: "/home/asus/Devagnos/almada/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php"
line: 623
trace: [,…]
i have disabled the timestamps and i set the dateformat like this:
protected $dateFormat = 'Y-m-d H:i:s.u';
public $timestamps = false;
protected $dates = [
'created_at',
'updated_at'
];
alse I added these methods
/**
* #param $val
*/
public function setCreatedAtAttribute($val)
{
return Carbon::createFromFormat('Y-m-d H:i:s.u', $val);
}
/**
* #param $val
*/
public function setUpdatedAtAttribute($val)
{
return Carbon::createFromFormat('Y-m-d H:i:s.u', $val);
}
but I am always getting the same error, What am I doing wrong ?
I'm using laravel 6.8 and postgresql
If you're trying to use microseconds, then you should refer to this guide from the documentation:
https://carbon.nesbot.com/laravel/
I don't get what you tried with setCreatedAtAttribute and setUpdatedAtAttribute, setters are supposed to change the inner property, not to return a value.
Then check you gave to your DB columns enough precision (such as TIMESTAMP(6)) in your migration schemas.

Error: DDL statements are not allowed in transactions while running setup:upgrade. Running Data Patch

Magento 2.3.1
After making the data patch at the following location:
vendor/ModuleName/Setup/Patch/Data/AddMyColumnPatch.php
(Code given below for AddMyColumnPatch.php)
When I run bin/magento setup:upgrade to get this patch installed I get following error at cli:
DDL statements are not allowed in transactions
I have used the following file as reference to add a column to my table using data patch:
vendor/magento/module-quote/Setup/Patch/Data/InstallEntityTypes.php
Follow lines from 47 to 65.
My AddMyColumnPatch.php code is:
<?php
namespace Vendor\ModuleName\Setup\Patch\Data;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
use Magento\Quote\Setup\QuoteSetupFactory;
use Magento\Sales\Setup\SalesSetupFactory;
use Psr\Log\LoggerInterface;
/**
*/
class AddressSuburbPatch implements DataPatchInterface, PatchRevertableInterface
{
/**
* Attribute Code of the Custom Attribute
*/
const CUSTOM_ATTRIBUTE_CODE = 'my_column';
/**
* #var \Magento\Framework\Setup\ModuleDataSetupInterface
*/
private $moduleDataSetup;
/**
* #var \Magento\Quote\Setup\QuoteSetupFactory
*/
private $quoteSetupFactory;
/**
* #var Magento\Sales\Setup\SalesSetupFactory
*/
private $salesSetupFactory;
/**
* #var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* #param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup
*/
public function __construct(
ModuleDataSetupInterface $moduleDataSetup,
QuoteSetupFactory $quoteSetupFactory,
SalesSetupFactory $salesSetupFactory,
LoggerInterface $logger
)
{
$this->moduleDataSetup = $moduleDataSetup;
$this->quoteSetupFactory = $quoteSetupFactory;
$this->salesSetupFactory = $salesSetupFactory;
$this->logger = $logger;
}
/**
* {#inheritdoc}
*/
public function apply()
{
$this->moduleDataSetup->getConnection()->startSetup();
$this->logger->debug('DDL Statements error');
$quoteSetup = $this->quoteSetupFactory->create(['setup' => $this->moduleDataSetup]);
$quoteSetup->addAttribute('quote_address', self::CUSTOM_ATTRIBUTE_CODE, ['type' => 'text']);
$salesSetup = $this->salesSetupFactory->create(['setup' => $this->moduleDataSetup]);
$salesSetup->addAttribute('order_address', self::CUSTOM_ATTRIBUTE_CODE, ['type' => 'text']);
$this->logger->debug('Script working');
$this->moduleDataSetup->getConnection()->endSetup();
}
/**
* {#inheritdoc}
*/
public static function getDependencies()
{
return [
];
}
public function revert()
{
$this->moduleDataSetup->getConnection()->startSetup();
$this->moduleDataSetup->getConnection()->endSetup();
}
/**
* {#inheritdoc}
*/
public function getAliases()
{
return [];
}
}
After going through declarative schema docs again and referencing core Magento code for quote module and PayPal module I have figured out that if you want to add a field into an existing table in Magento >=2.3 you should use configure declarative schema for that. Read more:
https://devdocs.magento.com/guides/v2.4/extension-dev-guide/declarative-schema/db-schema.html
So create a db_schema.xml file under Vendor/ModuleName/etc
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
<table name="quote_address" resource="checkout" comment="Sales Flat Quote Address">
<column xsi:type="varchar" name="suburb" nullable="true" length="255" comment="Suburb for Quote Address" />
</table>
<table name="sales_order_address" resource="sales" comment="Sales Flat Order Address">
<column xsi:type="varchar" name="mycolumn" nullable="true" length="255" comment="mycolumn for Sales Order Address" />
</table>
</schema>
Then generate whitelist for your db_schema as follows:
bin/magento setup:db-declaration:generate-whitelist --module-name=Vendor_ModuleName
Run again and your column will be added to quote_address and order_sales_address tables.
bin/magento setup:upgrade
However, further investigation revealed that there is no need of making data patch for adding columns in flat tables quote_address and sales_order_address. Only declaring columns in db_schema.xml will do the job.

sharing objects between doctrine2 odm and orm in symfony2 with doctrine extensions

in my project i need to share objects between orm and odm. I have an entity "Variation" and a document "Tracking". One variation can hold many trackingevents. I tried to solve it through doctrine extension references using the stofdoctrineextensionbundle for symfony 2.3 but whatever i do it wont work. Maybe someone of you have an idea.
Tracking Document: ( watch for $variation )
namespace Anchorbrands\Bundle\LandingpageBundle\Document;
use Anchorbrands\Bundle\LandingpageBundle\AnchorbrandsLandingpageBundle;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
/**
* #ODM\Document(collection="landingpage_tracking")
*/
class Tracking {
/**
* #Gedmo\ReferenceOne(type="entity", class="Anchorbrands\Bundle\LandingpageBundle\Entity\Variation", inversedBy="trackingEvents", identifier="variationId")
*/
protected $variation;
protected $variationId;
public function setVariationId($variationId)
{
$this->variationId = $variationId;
}
public function getVariationId()
{
return $this->variationId;
}
Variation Entity ( watch out for $trackingEvents )
/**
* Variation
*
* #ORM\Table(name="landingpage_variation")
* #ORM\Entity
*/
class Variation
{
/**
* #Gedmo\ReferenceMany(type="document", class="Anchorbrands\Bundle\LandingpageBundle\Document\Tracking", mappedBy="variation")
*/
protected $trackingEvents;
public function getTrackingEvents()
{
return $this->trackingEvents;
}
public function setTrackingEvents(Collection $trackingEvents)
{
$this->trackingEvents = $trackingEvents;
}
Workaround to the circular referencing problem (unless you can solve it using DI) #see stof's bundle
<?php
namespace ACME\CoreBundle\Listener;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class ReferencesListener
*
* #package ACME\CoreBundle\Listener
*/
class ReferencesListener extends \Gedmo\References\ReferencesListener
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
/**
* #var array
*/
protected $managers
= [
'document' => 'doctrine.odm.mongodb.document_manager',
'entity' => 'doctrine.orm.default_entity_manager'
];
/**
* #param ContainerInterface $container
* #param array $managers
*/
public function __construct(ContainerInterface $container, array $managers = array())
{
$this->container = $container;
parent::__construct($managers);
}
/**
* #param $type
*
* #return object
*/
public function getManager($type)
{
return $this->container->get($this->managers[$type]);
}
}
Listener defination
acme.listener.reference:
class: ACME\CoreBundle\Listener\ReferencesListener
arguments: ["#service_container"]
tags:
- { name: doctrine.event_subscriber, connection: default }
When using doctrine extension's references you need to make sure you have registered the subscriber with doctrine. Otherwise the references won't work.
In symfony2 you can register doctrine listeners/subscribers using container tags:
config.yml
services:
gedmo_reference_listener: # choose whatever name you like
class: Gedmo\References\ReferencesListener
arguments:
- { entity: #doctrine.orm.entity_manager, document: #doctrine.odm.mongodb.document_manager }
tags:
- { name: doctrine.event_subscriber, connection: default }
This is what StofDoctrineExtensionsBundle usually does in the bundle's compilerpass. It just simplifies the process of registering the services using a bit of configuration.
But as you can see here ... #stof didn't yet add the reference listener to the configuration options.
There is already an open pull request but #stof doesn't want to add it until the implementation has been refactored. Meanwhile use my solution above :-)
Maybe try from :
services:
# reference behavior doctrine < mongodb
gedmo.listener.reference:
class: Gedmo\References\ReferencesListener
tags:
- { name: doctrine_mongodb.odm.event_subscriber }
calls:
- [ setAnnotationReader, [ "#annotation_reader" ] ]
- [ registerManager, [ 'entity', "#doctrine.orm.default_entity_manager" ] ]
# reference behavior doctrine > mongodb
gedmo.listener.reference:
class: Gedmo\References\ReferencesListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "#annotation_reader" ] ]
- [ registerManager, [ 'document', "#doctrine_mongodb.odm.document_manager" ] ]
They both work for me, but alone...
If you put them both like this only the second is taken

Symfony2 and MongoDB Annotations / Mapping not recognized

I followed the steps descriped in the cookbook. But when i run doctrine:mapping:info or doctrine:generate:entities doctrine ignores my file.
Here is the code, maybe someone has an advice.
autoload.php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Composer\Autoload\ClassLoader;
/**
* #var $loader ClassLoader
*/
$loader = require __DIR__.'/../vendor/autoload.php';
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
AnnotationDriver::registerAnnotationClasses();
return $loader;
composer.json
"doctrine/orm": ">=2.2.3,<2.4-dev",
"doctrine/mongodb-odm": "1.0.*#dev",
"doctrine/mongodb-odm-bundle": "3.0.*#dev",
"doctrine/doctrine-bundle": "1.2.*",
"doctrine/doctrine-fixtures-bundle": "dev-master",
"doctrine/doctrine-migrations-bundle": "dev-master",
config.yml:
doctrine_mongodb:
connections:
default:
server: mongodb://localhost:27017
options: {}
default_database: test_database
document_managers:
default:
auto_mapping: true
entity:
<?php
// src/Acme/StoreBundle/Document/Product.php
namespace Anchorbrands\Bundle\LandingpageBundle\Entity;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class Product
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $name;
/**
* #MongoDB\Float
*/
protected $price;
}
wrong namespace/folder :)
namespace Anchorbrands\Bundle\LandingpageBundle\Entity;
should be
namespace Anchorbrands\Bundle\LandingpageBundle\Document;
The documents usually live in the Document folder.
Entity folder/namespace is for ORM not ODM.
Have a look at the documentation here.
Wrong command
for mongodb / doctrine odm you need to use ...
app/console doctrine:mongodb:generate:documents AnchorbrandsLandingpageBundle
... while doctrine:generate:entities is for doctrine orm.