Doctrine migrations fallback - php

We're using doctrine migrations and there often are problems when the migration contains multiple actions and one of them fails.
For example, if there is a migration adding 5 foreign keys and the 5th of them fails while fields aren't of the same length, fixing the error with the fields and regenerating migrations does not fix the whole thing, while now there is an error connected with the fact 4 of the keys already exists and don't allow the migration to run successfully.
Is there a stable way to use Doctrine migrations without such obvious problems as mentioned? We've used .sql files previosly, which aren't much better actually, but I'm pretty sure there is the right way of database versioning for a Doctrine-using project?
Generating migrations based on the difference between models and schema is great and I'd like to keep this possibility furthermore.
Thanks

I kind of solved this, the solution isn't all that nice, but still, I guess it will be useful to other people. I'm using CLI indeed I've already done the file making every migration update the number in the database, similar to the one in the Timo's answer before asking this question, but that still isn't very effective but worth doing anyway.
What I've done next kind of solves stuff, go to
doctrine/lib/Doctrine/Migration/Builder.php line 531. There is the definition of the default class every migration will extends. Since I'm using CLI and could not find a way to pass parameters to this place I've just replaced Doctrine_Migration_Base to another class MY_Doctrine_Migration_Base which is below.
If you're not using CLI I'd say you should try to pass options and not replace source.
So the below class extends Doctrine_Migration_Base and overwrites a bunch of methods to the ones, checking whether it's OK to make changes and then calling parent method to do them. It doesn't cover all the methods currently, just the ones I've encountered when I wrote this.
Now every migration Doctrine creates extends my class which is aimed at preventing the problems I mentioned originally.
<?php
class MY_Doctrine_Migration_Base extends Doctrine_Migration_Base {
public function __construct() {
$this->connection = Doctrine_Manager::getInstance()->getCurrentConnection();
}
public function addIndex($tableName, $indexName, array $definition) {
foreach ($this->connection->execute("SHOW INDEXES IN $tableName")->fetchAll(PDO::FETCH_ASSOC) as $index) {
if ($index['Key_name'] === $indexName.'_idx') {
echo "Index $indexName already exists in table $tableName. Skipping\n";
return;
}
}
parent::addIndex($tableName, $indexName, $definition);
}
public function removeColumn($tableName, $columnName) {
if ($this->column_exists($tableName, $columnName)) {
parent::removeColumn($tableName, $columnName);
} else {
echo "Column $columnName doesn't exist in $tableName. Can't drop\n";
}
}
public function createTable($tableName, array $fields = array(), array $options = array()) {
if ($this->connection->execute("SHOW TABLES LIKE '$tableName'")->fetchAll(PDO::FETCH_ASSOC)) {
echo "Table $tableName already exists. Can't create\n";
} else {
parent::createTable($tableName, $fields, $options);
}
}
public function addColumn($tableName, $columnName, $type, $length = null, array $options = array()) {
if (! $this->column_exists($tableName, $columnName)) {
parent::addColumn($tableName, $columnName, $type, $length, $options);
} else {
echo "Column $columnName already exists in $tableName. Can't add\n";
}
}
private function column_exists($tableName, $columnName) {
$exception = FALSE;
try { //parsing information_schema sucks because security will hurt too bad if we have access to it. This lame shit is still better
$this->connection->execute("SELECT $columnName FROM $tableName")->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $exception) {}
//if someone knows how to check for column existence without exceptions AND WITHOUT INFORMATION SCHEMA please rewrite this stuff
return $exception === FALSE;
}
}
Suggestions on how to improve this are welcome.

If you're using the doctrine-cli you can write your own migration task that backs up the database before the migration and restores the back up if the migration fails. I wrote something similar for our symfony/doctrine migrations.
If you put your task class in the correct directory the doctrine cli will display it in the list of available commands

Doctrine migrations can not handle this. Sorry to say that we all have these problems, because the migrations didn't run in a transaction.
You can improve this by adding a plugin. See: Blog-Post
The other possibility is to do a database backup before migrating and if something goes wrong you can reinstall the backup. You can automate this by a shell script

Related

Override mutator's value in eloquent

Given following code:
class Picture {
public function getAbsolutePathAttribute() {
return "absolute_path"
}
}
$picture = new Picture();
echo $picture->absolute_path; // prints "absolute_path"
$picture->absolute_path = "I will override you no matter what";
echo $picture->absolute_path; // prints "absolute_path"
Is there way of overriding an eloquent mutator attribute?
I have tried a magic method:
setAbsolutePathAttribute($value) {
$this["attributes"] = $value;
}
However it did not work.
So I don't recommend trying to solve this by overriding the mutator. You might be able to, but the less you touch the core of Laravel the better. Why? Because you never know what future Laravel code could look like.
A core change could possibly break your internal override the next time you do a minor or major Laravel version upgrade.
So, 2 ways to solve this, given your simple example:
1. Create a default value at the database level
Laravel migrations feature a default column modifier, it looks something like this: ->default('something')
That way you don't need the mutator at all for getting that default value you're looking for.
You can read more about them here: https://laravel.com/docs/5.6/migrations#column-modifiers
2. Make your accessor smarter
Your example is pretty simple, so I'll just work with it:
class Picture {
public function getAbsolutePathAttribute() {
if(is_null($this->absolute_path)) {
return "absolute_path";
}
return $this->absolute_path;
}
}
That way it only does something if there is no value present.

How do I connect to different databases at run time?

I am making a multi-tenant multi-database app that has one central database and many sub-databases.
The app creates an instance of an organisation in the central database, and to each organisation it creates a sub-database with different tables.
To achieve this, I have made a class class Setup that
Creates the Organisation
Creates its Database
Configures the connection to that database and connects to it
Runs the proper migrations to it.
All wrapped up in a constructor, so upon caling Setup::create all of this runs properly.
Most of the database configuration and connection are inspiried from this tutorial.
To test whether my logic goes as wanted, I interacted with my application via :
Tinker
Web Browser.
To my suprise, the outcome is different in both cases, and never as wanted as far as connecting to another database is concerned.
Interaction with tinker :
After creating calling my Setup::create and having output telling me everything went okay, I try to check for myself what database am I on right now Using:
DB::connection()->getDatabaseName()
It outputs the sub-database name we have just created for the organisation and connected to, which is logical and going accordingly.
However, I attempt to connect to another database by creating a new configuration for it and then connecting to it with the DB methods I have provided, it does not work, I am still on the sub-database I was on.
Interacting with the browser :
This time, having my Setup::create wrapped up properly in my controller's code, I attempt to test everything again, I also made a line in my layout to output me the current database :
<?php echo DB::connection()->getDatabaseName() ?>
At first, while I am still on the central database, its name appears, however after calling Setup::create, it switches to the sub-database -Which is expected- but then, after one refresh, I am on the central database again -Which is totally Unexpected-
So, what happens here? and how do I get to connect to all of my different databases how I wish when I wish?
Extra:
Testing in tinker, I have went to the point where I have commented out the migration code, and left the creation of the database and also the connection to it.
To my suprise, it does not connect to the database.
so I started thinking that the migration code has something to do with connecting to the database, or maybe tinker has different behaviors I completely ingore.
Important:
I have came across threads where solutions using QueryBuilders were mentioned
Please, do not provide such answers because my aim is to switch databases entirely to the point where I can use eloquent model's events with no problem.
By that I mean, I want to be able to use Model::create after having connected to the database instead of DB::connection()->....
Technical details:
I am using Laravel 5 with mysql-server, on Ubuntu Machine.
I stumbled upon this question and it had my answer.
I made a class called DatabaseConnection:
class DatabaseConnection extends Model
{
static $instances=array();
protected $database;
protected $connection;
public function __construct($options = null)
{
// Set the database
$database = $options['database'];
$this->database = $database;
// Figure out the driver and get the default configuration for the driver
$driver = isset($options['driver']) ? $options['driver'] : Config::get("database.default");
$default = Config::get("database.connections.$driver");
// Loop through our default array and update options if we have non-defaults
foreach($default as $item => $value)
{
$default[$item] = isset($options[$item]) ? $options[$item] : $default[$item];
}
$capsule = new Capsule;
$capsule->addConnection($default);
$capsule->setEventDispatcher(new Dispatcher(new Container));
$capsule->setAsGlobal();
$capsule->bootEloquent();
// Create the connection
$this->connection = $capsule->getConnection();
DatabaseConnection::$instances[] = $capsule;
return $this->connection;
}
}
So, whenever I am in a controller that manipulates tables of a sub-database, I simply go this way:
public function RandomActionInMyController()
{
$db_connection = new DatabaseConnection(['database' => 'name_of_db']);
$someModel = new Model/Model::find()..// Basically anything
return myreturnstuff;
}
Extra Bonus:
The use of the static attribute $instances in my DatabaseConnection
boils down to retrieving my latest database connection for ease uses.
For example, if I ever wanted to retrieve it, it would be wrapped in a function such as
function CurrentOrLatestDbConnection()
{
if( !empty(DatabaseConnection::$instances) )
{
return end(DatabaseConnection::$instances)->getConnection()->getDatabaseName();
}
}
Notes :
If you encounter errors such as Unknown class 'Container' or Capsule or anything of that kind, make sure you check the question link I have provided, and use use statements properly.
Concerning upcoming answers :
It seems to me that this database connection lives within the the brackets of the controller's action, so when I proceed to another action that specifies no connection, it returns to the central database automatically.
Which has got me thinking that there must be a way to set the database connection to the sub-database in a 'global' way to the whole function, such as a middleware or something.
I would love to see an answer, implementing such thing.
Update :
I came up with a neater way to do it.
I assume you are on the same ground as me, wanting to change databases conditionally in accordance with each controller... say, each of your controllers requires a different database, just for the sake of the argument.
What we will be using to solve this is `Middlewares.
First, to explain what we are about to do..
We are going to check for the name of the controller (and even action) and then set the proper database we wish to set.
Go to your command-line , type in:
php artisan make:middleware SetDatabaseConnectionMiddleware
To create a middleware with ready boilerplate.
Or, if you like it the hard way, go to your app_name/app/Http/Middleware and create one manually.
Go to your helper methods file( if you already have one, if not, dude make one!)
function getControllerAndActionName()
{
$action = app('request')->route()->getAction();
$controller = class_basename($action['controller']);
list($controller, $action) = explode('#', $controller);
return ['action' => $action, 'controller' => $controller];
}
This will return to you an array with both the action name and controller name, if you want to return restrictidly just the controller's name, feel free to remove 'action' => $action from the code.
Inside of your middleware, it will look this way :
namespace App\Http\Middleware;
use Closure;
use DatabaseConnection;
class SetProperDatabase
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$database_name = '';
$controllerAndActionName = getControllerAndActionName();
$controller_name = $controllerAndActionName['controller'];
$action_name = $controllerAndActionName['action'];
if($controller_name == 'my_controller_nameController')
{
$database_name = 'your_proper_database_name';
}
else
{
$database_name = 'other_db';
}
$database_connection = new DatabaseConnection(['database' => $database_name']);
return $next($request);
}
}
4.Now, that you have created properly your middleware, let us tell your app where to find it and under what name.
Go to your app_name/app/Http/Kernel.php
In your $routeMiddleware variable, add this line
'set_proper_database' => \App\Http\Middleware\SetProperDatabase::class,
This way we know how to call it.
Finally, setting it up.
Go to your Controller.php (the Abstract class from which all of your controller's inherit)
public function __construct()
{
$this->middleware('set_proper_database');
}
And this should do it for you.
If you have any further questions, please feel free to comment.
// Resources :
1.Controller And Action Name
2.Middleware Documentation
3.Further Middleware Documentation
Notes :
I'd appreciate some edition concerning my styling and code indenting, since it seems I struggled to style my code properly in here but in vain, the indentions I used had no effeft.

How to put begin-commit transaction in controller: cakephp?

I'm working on a controller that will update a few tables. I am able to call my model from my controller and inside the model function I can make a begin and commit my query, it can rollback should an error happen.
Here is my sample:
Controller:
//update table when update button is clicked
if (!empty($this->data)) {
if ($this->Item->update($this->data)) {
$this->Item->create();
$this->redirect('/sample');
return;
} else {
$this->set('data', $this->data);
}
}
Model:
function update($data)
{
$this->begin($this);
if(!parent::save($data)) {
$this->rollback($this);
return false;
}
$this->commit();
return true;
}
Now this works fine. But what I need to do is to call another model in my controller like "$this->"ANOTHER MODEL HERE"->update()". I need to have rollback should a problem occur with either model transaction. What I'm thinking is to put a commit in my controller after both model call succeeds.
Much like this:
CONTROLLER PHP:
BEGIN TRANSACTION
->CALLS MODEL1
IF(MODEL1 == ERROR){
ROLLBACK
}
->CALLS MODEL2
IF(MODEL2 == ERROR){
ROLLBACK
}
COMMIT WHEN NO PROBLEM IS ENCOUNTERED
So is it possible to perform commit in controller? I am only able to do it in model. Thanks in advance!
So is it possible to perform commit in controller? I am only able to do it in model.
Yes, you can perform commit or rollback from within the controller. You need to get the datasource from one of your models first. In the controller code, simply reference one of the models you are using (assuming they are all in the same database):
$ds = $this->MyModelName->getdatasource();
Then you can begin, commit, and rollback to that datasource from within the controller.
$ds->begin();
// do stuff and save data to models
if($success)
{
$ds->commit();
}
else
{
$ds->rollback();
}
I actually have a rollback or commit in more than one place if I am bailing on the action and redirecting or finalizing in some step and redirecting. I just illustrate a simple case here.
Handling transactions in the controller makes the most sense to me since the controller action is where the transaction boundaries really reside conceptually. The idea of a transaction naturally spans updates to multiple models. I have been doing this using postgres as the back end database with Cake 2.2 and 2.3 and it works fine here. YMMV with other db engines though I suspect.
Trasactions are to be enhanced in futures versions of CakePHP, as you can see in this CakePHP Lighthouse ticket.
There are two possible solutions proposed there, and I am showing you a third one. You could create a custom method to save it, and manually commit the transactions:
public function saveAndUpdate($data) {
$ds = $this->getDataSource();
$ds->begin();
if ($this->save($data)) {
foreach(Array('Model1', 'Model2') as $model) {
if (!ClassRegistry::init($model)->update()) {
$db->rollback();
return false;
}
}
return $db->commit() !== false;
}
return false;
}
I wrote this code to illustrate how I though about your problem, although I didn't test.
More useful links:
Transactions at CakePHP Book
About CakePHP Behaviors
How to create Behaviors
I used commit within my if statements and rollback in my else statements. Since I was using two different models from within a controller, I created two different datasources
$transactiondatasource = $this->Transaction->getDataSource();
$creditcarddatasource = $this->Creditcard->getDataSource();
$transactiondatasource->begin();
$creditcarddatasource->begin();
if (CONDITION){
$creditcarddatasource->commit();
$transactiondatasource->commit();
CakeSession::delete('Cart');
} else {
$this->Session->setFlash(__('MESSAGE'));
$creditcarddatasource->rollback();
$transactiondatasource->rollback();
}

Check if database tables exists - performance?

I want to make some simple objects/models for unspecified frameworks/systems.
Additionally I want to use MySQL as backend for my data.
My goal is simple implementation - small changes to a configuration file and thats basically it.
My problem is that I'm convinced I need to check, when using my model, that the database tables actually exists - and if not, create the table(s) for me.
I was thinking something like:
<?php
class MyObject
{
public function __construct()
{
$dal->query("SHOW TABLES LIKE 'MyTable'");
if($dal->num_rows() == 0)
$this->_createTables();
}
...
}
?>
But I'm worried about the performance with this model - I'm looking for either confirmation on the efficiency of my solution or a better solution.
In my opinion, and depending on your application's needs, you might be better off checking for this condition only if an error occurs, and to assume that the table exists otherwise. Something like this would avoid the extra query on every instance (you should, however, put the check into its own method).
public function insert($data, $spiralingToDeath=false)
{
// (do actual insertion here)
if ($this->isError) {
// nothing obvious
if ($spiralingToDeath) {
// recursion check
throw new DBException("Tried to create a table and failed.");
} else {
$dal->query("SHOW TABLES LIKE 'MyTable'");
if($dal->num_rows() == 0) {
$this->_createTables();
}
// try again:
$this->insert($data, true);
}
}
}
What about CREATE TABLE IF NOT EXISTS?
Require the user to change the configuration through your editor and make your editor modify the table layout before it saves the configuration.
also, you may want to consider just calling 'show tables', then caching it at the class level in a private var, so you could do a simple isset later, eg.
isset($this->tables_cache[$db_name][$table_name])
this would allow you to scale a bit better with more tables.
you could also save this as a json structure or serialized struct in your filesystem, loading it on __construct of your class, and (re)saving it on __destruct.

Symfony Model Callback Equivalent

I'm working on a Symfony project (my first) where I have to retrieve, from my Widget class, a set of widgets that belong to a Page. Before returning the results, though, I need to verify--against an external service--that the user is authorized to view each widget. If not, of course, I need to remove the widget from the result set.
Using CakePHP or Rails, I'd use callbacks, but I haven't found anything similar for Symfony. I see events, but those seem more relevant to controllers/actions if I'm reading things correctly (which is always up for discussion). My fallback solution is to override the various retrieval methods in the WidgetPeer class, divert them through a custom method that does the authorization and modifies the result set appropriately. That feels like massive overkill, though, since I'd have to override every selection method to ensure that authorization was done without future developers having to think about it.
It looks like behaviors could be useful for this (especially since it's conceivable that I might need to authorize other class instances in the future), but I can't find any decent documentation on them to make a qualified evaluation.
Am I missing something? It seems like there must be a better way, but I haven't found it.
First of all, I think behavior-based approach is wrong, since it increases model layer coupling level.
There's sfEventDispatcher::filter() method which allows you to, respectively, filter parameters passed to it.
So, draft code will look like:
<somewhere>/actions.class.php
public function executeBlabla(sfWebRequest $request)
{
//...skip...
$widgets = WidgetPeer::getWidgetsYouNeedTo();
$widgets = $this->getEventDispatcher()->filter(new sfEvent($this, 'widgets.filter'), $widgets));
//...skip...
}
apps/<appname>/config/<appname>Configuration.class.php
//...skip...
public function configure()
{
$this->registerHandlers();
}
public function registerHandlers()
{
$this->getEventDispatcher()->connect('widgets.filter', array('WidgetFilter', 'filter'));
}
//...skip
lib/utility/WidgetFilter.class.php
class WidgetFilter
{
public static function filter(sfEvent $evt, $value)
{
$result = array();
foreach ($value as $v)
{
if (!Something::isWrong($v))
{
$result[] = $v;
}
}
}
}
Hope you got an idea.
Here's some documentation on Symfony 1.2 Propel behaviors: http://www.symfony-project.org/cookbook/1_2/en/behaviors.
Why not just have a 'getAllowedWidgets' method on your Page object that does the checks you're looking for? Something like:
public function getAllowedWidgets($criteria = null, PropelPDO $con = null) {
$widgets = $this->getWidgets($criteria, $con);
$allowed = array();
// get access rights from remote service
foreach($widgets as $widget) {
// widget allowed?
$allowed[] = $widget;
}
return $allowed;
}
However, if you always want this check to be performed when selecting a Page's Widgets then Propel's behaviours are your best bet.
Although, at least in theory, I still think that a behavior is the right approach, I can't find a sufficient level of documentation about their implementation in Symfony 1.4.x to give me a warm and fuzzy that it can be accomplished without a lot of heartburn, if at all. Even looking at Propel's own documentation for behaviors, I see no pre- or post-retrieval hook on which to trigger the action I need to take.
As a result, I took my fallback path. After some source code sifting, though, I realized that it wasn't quite as laborious as I'd first thought. Every retrieval method goes through the BasePeer model's doSelect() method, so I just overrode that one in the customizable Peer model:
static public function doSelect( Criteria $criteria, PropelPDO $con = null ) {
$all_widgets = parent::doSelect( $criteria, $con );
$widgets = array();
foreach ( $widgets as $i => $widget ) {
#if( authorized ) {
# array_push( $widgets, $widget );
#}
}
return $widgets;
}
I haven't wired up the service call for authorization yet, but this appears to work as expected for modifying result sets. When and if I have to provide authorization for additional model instances, I'll have to revisit behaviors to remain DRY, but it looks like this will suffice nicely until then.

Categories