Symfony/Doctrine keeps creating the same migration - php

Edit: Turns out other projects on the same machine are having the same issue. The question is for one specific project:
I have a Symfony4 project in which I have a few entities (created via make:entity). I just noticed that the last 4 migrations have the exact same query in them (and yes, I run migrations in between). Example:
$this->addSql('ALTER TABLE event CHANGE visible_from visible_from DATETIME DEFAULT NULL, CHANGE visible_till visible_till DATETIME DEFAULT NULL, CHANGE max_signups max_signups INT DEFAULT NULL');
I ran that query manually and that actually updated the table. I then created a new migration to test: The above query was there, again.
I've cleared cache, ran doctrine:cache:clear-metadata, double checked for weird stuff, but everything is normal.
I have the same 3 queries every time and they all have one thing in common: They have nullable=true. All other entities do not have nullable=true in them.
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $maxSignups;
The downmigration might be a hint:
ALTER TABLE event CHANGE visible_from visible_from DATETIME DEFAULT \'NULL\',
This I find odd---^
MariaDB version: 10.2.14
PHP: 7.2.4
symfony/orm-pack: "^1.0"
Anyone who knows why?

I still dont really know why ( So I'll leave this question open for a bit), but this is what I did:
I was running both MySQL and MariaDB and it defaulted to MySQL. Turned that off, switched to MariaDB and adding a version in doctrine.yaml:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_mysql'
server_version: 'mariadb-10.2.14'
It doesnt really care about the version though. I made a typo and wrote 12.2.14 and it worked absolutally fine. I'm guessing it has a version>$n check somewhere.

Everything is explained here:
https://github.com/symfony/symfony-docs/pull/9547#issue-179389686
The most important is setting server_version with prefix "mariadb", the MariaDB version is less important.
Alternatively, you may not set server_version at all, then it should be detected automatically.
Below is the DBAL driver function responsible for parsing server_version:
public function createDatabasePlatformForVersion($version)
{
$mariadb = false !== stripos($version, 'mariadb');
if ($mariadb && version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) {
return new MariaDb1027Platform();
}
if ( ! $mariadb && version_compare($this->getOracleMysqlVersionNumber($version), '5.7.9', '>=')) {
return new MySQL57Platform();
}
return $this->getDatabasePlatform();
}
And here is the link to the whole class: https://github.com/doctrine/dbal/blob/f76bf5ef631cec551a86c2291fc749534febebf1/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php#L133

Related

Why/how does this work: Symfony 2.8 + FOSRestBundle automatically deserializes request object

I am working on migrating an existing Symfony 2.8 which I did not create. The previous developers are not available for questions.
Most of the code easy to understand, but at one point I simply cannot figure out how are why this works:
A controller/action parameter is automatically de-serialized from JSON data to a custom object. This is nothing special, I do not understand how/where the Symfony is told what to do. Form my point of view important config data is missing but it works anyway.
That's would be fine, but without understanding why it works in this case, I cannot figure out why it does not work anymore when migrating the project to Symfony 3.4...
Sorry for the (very) long question, but I tried different ways to solve the problem and answer the questions but they all resulted in different problems...
Using the following Bundles
sensio/framework-extra-bundle v3.0.29
friendsofsymfony/rest-bundle 2.4.0
jms/serializer 1.13.0
jms/serializer-bundle 1.5.0
Config:
// app/config/config.yml
sensio_framework_extra:
request: { converters: true }
fos_rest:
...
body_converter:
enabled: true
#jms_serializer: (not configured)
# ...
Code:
// src/AppBundle/Util/SortInfo.php
class SortInfo {
public $sortOrder;
public $sortBy;
}
// src/AppBundle/Resources/serializer/Util.SortInfo.yml
AppBundle\Util\SortInfo:
exclusion_policy: ALL
properties:
sortOrder:
type: string
expose: true
sortBy:
type: string
expose: true
// src/AppBundle/Controller/SortingController.php
class SortingController extends Controller {
...
/**
* #FOSRest\View()
*/
public function sortAction(SortInfo $sortInfo) {
$this->logger->info("sortAction");
$this->logger->info(" ".gettype($sortInfo)." -- ".get_class($sortInfo));
$this->logger->info(" ".$sortInfo->sortOrder." -- ".$sortInfo->sortBy);
...
}
}
That's it. After digging for some hours I could not find any explicit configuration that would tell the sortAction method to convert the JSON data from the request automatically. But it works.
A request with the following JSON content results in the following log output:
// JSON content
{"sortOrder":"ASC", "sortBy":"name"}
// Log output
sortAction
object -- AppBundle\Util\SortInfo
ASC -- name
Why does this work?
As said before this is fine, but the problem is, that the same code does not work with Symfony 3.4 and newer versions of FOSRest, ExtrasBundle and JMSSerializer:
sensio/framework-extra-bundle v5.2.4
friendsofsymfony/rest-bundle 2.5.0
jms/serializer 2.1.0
jms/serializer-bundle 3.0.0
A request with the same JSON data results in the following log output in `Symfony 3.4':
// Log output
sortAction
object -- AppBundle\Util\SortInfo
--
So although the SortInfo is created (not null), the sortBy and sortOrder properties are empty/null.
How can this be? I do not understand why the data is de-serialized in the first place, but how it is possible to create a SortInfo object without setting the properties is even more mysterious.
In Symfony 2.8 the Util.SortInfo.yml file is necessary for the process. Removing this files results in an exception:
Symfony\Component\HttpKernel\Exception\BadRequestHttpException: "You
must define a type for
AppBundle\Util\SortInfo::$sortBy."
at /.../vendor/friendsofsymfony/rest-bundle/Request/RequestBodyParamConverter.php
Here I do not understand why SortInfo::$sortBy is a problem but not SortInfo it self. Additionally removing the file in Symfony 3.4 does not change anything.
But at least this error message confirms, that the FOS RequestBodyParamConverter is involved in the process.
Following the Symfony docs it should be necessary to manually specify that the parameter that should be converted (although it works just fine without this config in Symfony 2.8):
/**
* #FOSRest\View()
* #ParamConverter("sortInfo", converter="fos_rest.request_body")
*/
public function sortAction(SortInfo $sortInfo) {
...
}
However, adding this config results in another error in Symfony 3.4:
Uncaught PHP Exception RuntimeException: "Converter
'fos_rest.request_body' does not support conversion of parameter
'$sortInfo'
Bottom line:
This is all very mysterious.
Why does it work without any problem in Symfony 2.8 although regarding to the docs essential config is missing (explicitly specify ParamConverter)
Why does it NOT work in Symfony 3.4?

Symfony2 - Custom DQL Function Registered But Does Not Exist

I am desperatelly trying to include the LEVENSHTEIN function in Symfony2, however, I still receive errors. Specs + what I've done so far:
PostgreSQL 9.3
LEVENSHTEIN included in fuzzystrmatch extension
Tested the function via shell execution. Works perfectly fine:
postgres=# SELECT levenshtein('test', 'text');
levenshtein
-------------
1
(1 row)
Added the function in DQL:
<?php
namespace AppBundle\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
class LevenshteinFunction extends FunctionNode {
public $firstStringExpression = null;
public $secondStringExpression = null;
public function getSql(SqlWalker $sqlWalker) {
return 'LEVENSHTEIN(' . $this->firstStringExpression->dispatch($sqlWalker) . ', ' . $this->secondStringExpression->dispatch($sqlWalker) . ')';
}
public function parse(Parser $parser) {
// levenshtein(str1, str2)
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstStringExpression = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->secondStringExpression = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
Config.yml
orm:
auto_generate_proxy_classes: "%kernel.debug%"
auto_mapping: true
dql:
numeric_functions:
LEVENSHTEIN: AppBundle\DQL\LevenshteinFunction
Problem: When executing the following codeblock in my Repository, the following errors occur:
$this->getEntityManager()->createQuery("SELECT LEVENSHTEIN('test', 'text') FROM AppBundle:User");
return $query->getResult();
SQLSTATE[42883]: Undefined function: 7 ERROR: function levenshtein(unknown, unknown) does not exist
What am I missing? Why isn't DQL/Symfony/PDO/... recognizing the function? Any help is highly appreciated!
The error comes from Postgres, seems like a problem with visibility.
The additional module fuzzystrmatch has to be installed, of course. You obviously did that, or your function call would not work in psql either.
How to exclude PL/pgSQL functions in export?
If it works in psql, but not in your app, only a few possible explanations remain. The obvious first:
You are connected to the same database? (Same server, same port, same db?)
You are connecting with the same user? Probably not ...
If connecting with a different user (but in any case), check whether you are working the same search path. Run in either connection and compare:
SHOW search_path;
Details - and how to set the search_path:
How does the search_path influence identifier resolution and the "current schema"
Be aware that extensions can be installed to any schema of your choice. Default is the first schema in the search_path (the "current schema" at the time of the install, which is typically public, but I don't know that about your installation. The documentation:
If not specified, and the extension's control file does not specify a
schema either, the current default object creation schema is used.
Run this to diagnose a couple of things:
SELECT e.extname AS extension, nsp.nspname AS schema
, r.rolname AS schema_owner, nsp.nspacl AS schema_acl
FROM pg_extension e
JOIN pg_namespace nsp ON nsp.oid = e.extnamespace
JOIN pg_roles r ON r.oid = nsp.nspowner
You get something like:
extension | schema | schema_owner | schema_acl
---------------+------------+--------------+-------------------------------------
adminpack | pg_catalog | postgres | {postgres=UC/postgres,=U/postgres}
plpgsql | pg_catalog | postgres | {postgres=UC/postgres,=U/postgres}
fuzzystrmatch | public | postgres | {postgres=UC/postgres,=UC/postgres}
tablefunc | public | postgres | {postgres=UC/postgres,=UC/postgres}
...
If schema_acl includes =U/postgres (U for USAGE), then the public role has access, i.e. everybody.
Set the search_path for your connection accordingly or (re-)install to a visible schema and it should work.
Theoretically, the owning role or a superuser might have revoked the EXECUTE permission from the function itself ...
Your function class seems ok to me, but your configuration might be wrong. This is what I have for my CAST function:
doctrine:
orm:
dql:
string_functions:
CAST: App\MyBundle\Doctrine\DBAL\Functions\Porgres\Cast
You should note that you have different collections for the different types of functions i.e. string_functions,numeric_functions, datetime_functions. All of them are listed in the official documentation.
Other than that, your code should be working just fine after you clean your cache.

laravel 'class not found' error on production

Slightly odd one here.
I have Persons and Actions. Persons can have many Actions, while each Action belongs to only one Person. I'm using Chumper's Datatables to display a list of people, including a count of their actions.
Since migrating to a production (forge) server, I'm getting
Symfony \ Component \ Debug \ Exception \ FatalErrorException (E_ERROR)
Class 'action' not found
when calling the datatable. The error shown
/­vendor/­laravel/­framework/­src/­Illuminate/­Database/­Eloquent/­Model.php:721
public function hasOne($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$localKey = $localKey ?: $this->getKeyName();
suggests it's a problem with my hasMany relationship:
# /models/Person.php
class Person extends Eloquent {
public function actions()
{
return $this->hasMany('Action');
}
# /models/Action.php
class Action extends Eloquent {
public function person()
{
return $this->belongsTo('Person', 'person_id');
}
I assume these are fine, however, as it all works locally. Datatables also works fine elsewhere, calling through other items and their related actions with no trouble.
I've tried composer dump-autoload and artisan dump-autoload on the production server, to no avail. The deployment script on forge is below:
git pull origin master
composer install
php artisan migrate --env=production
I can't tell if it's a config issue, a database issue, a code issue or something else entirely. I've been back through the many similar questions but nothing's jumped out. Any help much appreciated.
for who may have the same problem, triple check the casing of your model! I had it wrong, that's why locally on mac was working but not on the server
So I think I'd borked this one myself.
I'd been lazy and left function datatablePersons() in PersonsController.php using an old 'count' method, relying on long-defunct relationships (that caused n+1, so had to be binned), hence it wobbling over an actions class whenever that relationship was called upon.
Datatable functions in other controllers (with a cleaner 'count' method) work fine, so I've just rewritten datatablePersons() to use the same method.
I've not quite got the query right (in eloquent, at least) yet - see this question here: mysql join ON and AND to laravel eloquent - but the class not found error has certainly gone away.
I'm (massively) guessing that the classmap on the local machine hadn't been flushed since whatever was removed was removed, while the production machine is rebuilt every push, hence the disparity...?
Either way, it's no longer an issue.

CakePHP - how to simply reset cached database models

I have several apps based on CakePHP and this basically applies to all of them. When my debug mode is set to 0 (live mode), every time I update the database structure, like new tables and fields, then as soon as my app uses those, I always get the default "An Internal Error Has Occurred" message. It is solved if I set debug to 1 and then use those new fields. Is there a better way to do this? I don't want to enable debugging and doing a test write every time I have to update my database. Also /tmp/cache subfolders are empty, so I don't know where it is stored.
Here's a function I wrote to do exactly that.
function clear_cache() {
$cachePaths = array('js', 'css', 'menus', 'views', 'persistent','models');
foreach($cachePaths as $config) {
clearCache(null, $config);
}
}
It uses the clearCache function in Cake.
You need to clear all cache configs
bin/cake cache clear_all
Source
For cake 2.x, you can delete the cache directory like this:
rm -rf app/tmp/cache/
For CakePHP 2.x, place this line of code anywhere in your application to clear the model cache:
Cache::clear(false, '_cake_model_');
This is decoupled from the low-level cache engine (File, Memcache, Redis, etc), so it should work as-is.
CakePHP 2.x Docs: Caching

Symfony/Propel 1.4: Read from one, write to other DB

We have an existing project (SNS website+android/Iphone games) in Symfony 1.4/ Propel 1.4
We are experiencing extra load on DB server (say DB1). We are doing DB Optimization but as immediate solution we decided to create one more DB server in the way DB2 is exact replica of DB1 all the time. Currently we have only DB1, used for both read and write operations.
Now we need to move all read operations to DB2 and keep write operations (generally in transactions) on DB1 as it is now.
What are the possible ways to make those changes (On production server without much downtime) and if possible, with minimal code changes.
Edit after first comment
Based on link given by J0k and some other links, I'd done following on local dev environment.
Created a test symfony 1.4 project
Updated database.yml as follow
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzo;'
username: root
password: mysql
encoding: utf8
persistent: true
pooling: true
slaves:
slave1:
dsn: 'mysql:host=localhost;dbname=wzoslv;'
username: root
password: mysql
encoding: utf8
Where database wzoslv is exact replica of database wzo except change in one test entry. On table odd_play row 26 (PK) column result entries are WON1 and WON respectively.
run symfony tasks
php symfony propel:build-schema
php symfony propel:build-model
php symfony cc
Created a module and added following code:
class wzoActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$con_write = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
$con_read = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_READ);
$oddPlay = OddPlayPeer::retrieveByPK(26,0,$con_write);
echo "on write connection, result=".$oddPlay->getResult();
$oddPlayRead = OddPlayPeer::retrieveByPK(26,0,$con_read);
echo "<br/>on Read connection, result=".$oddPlayRead->getResult();
exit;
$this->setLayout('layout');
}
}
Run http://local.sftest.com/index.php/wzo/index in the browser, output was,
on write connection, result=WON //Correct expected output
on Read connection, result=WON //Not correct. That should be WON1
I guess passing OddPlayPeer::DATABASE_NAME while creating both read/write connection is the issue but that how it was suggested in online examples. Can someone please suggest where I'm making the mistake?
Edit: Few more input
I updated debug echos in lib\vendor\symfony\lib\plugins\sfPropelPlugin\lib\vendor\propel\Propel.php to check how it is returning the connection. Found that it is entering in following if (line 544-549)
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
if (empty($slaveconfigs)) {
echo "inelseifif<br/>";// no slaves configured for this datasource
self::$connectionMap[$name]['slave'] = false;
return self::getConnection($name, Propel::CONNECTION_WRITE); // Recurse to get the WRITE connection
}
where $slaveconfigs are empty so returning write connection. Now the question is, why slaveconfigs is empty?
I also try editing sfDatabaseConfigHandler.class.php as defined in old forums but doing so, break symfony somewhere and nothing gets display on web and even in logs.
I'm sure I'm doing some mistake but whatever suggested on official documents of Propel/symfony and even here at stackoverflow, seems not working for me. Probably official documents should take better care of programmers who do not have lot of symfony experience.
Although we do not prefer to edit core files of any framework/third party libraries but this force me to edit core files to make a working solution for me. The solution that worked for me is as follow:
database.yml
My database.yml file is as follow:
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzo;'
username: testuserwzo
password:
encoding: utf8
persistent: true
pooling: true
slave:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzoslv;'
username: testuserwzoslv
password:
encoding: utf8
persistent: true
pooling: true
After that, I edited Propel.php file as follow
For Propel 1.4
File: lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel/Propel.php
Change line 542-543
// we've already ensured that the configuration exists, in previous if-statement
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
with (added one line inbetween)
// we've already ensured that the configuration exists, in previous if-statement
self::$configuration['datasources'][$name]['slaves'] = isset(self::$configuration['datasources']['slave']) ? self::$configuration['datasources']['slave'] : null;
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
Then in same file, changed line 560
$con = Propel::initConnection($conparams, $name);
to
$con = Propel::initConnection($conparams, 'slave'); //I know its bad practive to put hard-coded value but at that moment, I was more interested in working solution without caring about best practices.
For propel 1.6 (We upgraded propel just to make this working but reverted back to propel 1.4 later as upgrade on production needs to be well tested.)
File: plugins/sfPropelORMPlugin/lib/vendor/propel/runtime/lib/Propel.php
Changed line 601
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
to (Added one line before)
self::$configuration['datasources'][$name]['slaves'] = isset(self::$configuration['datasources']['slave']) ? self::$configuration['datasources']['slave'] : null;
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
Then in same file, changed line 629
$con = Propel::initConnection($conparams, $name);
to
$con = Propel::initConnection($conparams, 'slave');
Then following test file was giving expected result
class kapsActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$con_write = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
$con_read = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_READ);
$oddPlay = OddPlayPeer::retrieveByPK(28,0,$con_write);
echo "on write connection, result=".$oddPlay->getResult().$oddPlay->getPlayscore();
$oddPlayRead = OddPlayPeer::retrieveByPK(27,0,$con_read);
echo "<br/>on Read connection, result=".$oddPlayRead->getResult().$oddPlayRead->getPlayscore();
exit;
//$this->setLayout('layout');
}
}
I still do not recommend editing core files but this solution worked for us as in emergency condition. Someone else, if needed, may use it in emergency condition. Still looking for perfect solution.
You should add a new level to slaves plus a connection entry, according to the doc (on github)
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzo;'
username: root
password: mysql
encoding: utf8
persistent: true
pooling: true
slaves:
connection:
slave1:
dsn: 'mysql:host=localhost;dbname=wzoslv;'
username: root
password: mysql
encoding: utf8

Categories