doctrine 2 prevent LifecycleCallbacks caching of methods (or similar problem) - php

I'm using doctrine2 (2.0.3) with codeigniter, and I noticed that when I add, change or even remove some methods that are anotaded as lifecycle, sometimes doctrine just ignores the change. For example, I add
/*
* #PostLoad
*/
private function setUpObj() {
echo('in');
}
To the Model (entity) that #HasLifecycleCallbacks, function sometimes is called on postload, sometimes it is ignored, sometimes it accept one change, then ignores any other changes...
In bootstrap file I use some of the config options, here is sample of them, if more needed I will update my post
$cache = new \Doctrine\Common\Cache\ArrayCache;
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Set up driver
$Doctrine_AnnotationReader = new \Doctrine\Common\Annotations\AnnotationReader($cache);
$Doctrine_AnnotationReader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
$driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($Doctrine_AnnotationReader, APPPATH.'models');
$config->setMetadataDriverImpl($driver);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
$config->setAutoGenerateProxyClasses( TRUE );
After some time, (usually when I give up changing the method, take a walk and come back) it starts to work normally, it accepts my last change and then I usually create what my intention was and stop changing that method.
My server is standard/default xampp on win7, and I never noticed anything similar to any other php files so far. This is not related only to #PostLoad, but it happens with #PrePersist and #PreUpdate as well
Is this normal behavior, or am I missing something?
Thanks in advance,
Dalibor

It seems that notation and comment MUST be like this
/**
* #PostLoad
*/
function setUpObj() {
$this->mainObjName = 'models\Page';
$this->defaultSortingField = 'ordering';
}
Meaning, first comment line must start with /** (two stars) and function cannot be private. Or at least this is how it works for me, hopefully it helps someone else

Related

Updating data after backend action TYPO3 11.5

Working on Typo3 11.5.13
I'm trying to update some data on my pages table after a be_user changed something.
I read something about setting hooks for that purpose but I can't seem to find a good explanation as to how hooks actually function within Typo3 and how to configure one, especially for my purpose.
As far as I can see, this problem I have should be quickly solved but the complexity of the typo3 doc is hindering my progress again. Maybe you can explain how I can accomplish my goal.
Simply put: A backend user is supposed to choose a date in a datepicker and some dateinterval in the settings of a page. After saving(Or even after picking both values) I would like to update the "Next time happening" field the user can see but not change to be updated to the given date plus the dateinterval chosen.
If you have some sort of idea please share it with me.
Generally hooks are not that good documented. Modern Events are easier to find and better commented. However, if I get your use case right, using DataHandler Hooks are they way to go. That mean, every place which are using the DataHandler to save data are then covered. The backend form engine are using DataHandler.
Basic information about hooks in the core documentation:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Hooks/Index.html
How to identify or find hooks, events, signalslots (depending on TYPO3 version):
https://usetypo3.com/signals-and-hooks-in-typo3.html
https://daniel-siepmann.de/posts/migrated/how-to-find-hooks-in-typo3.html
Introduction or "DataHandler" explained:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Typo3CoreEngine/Database/Index.html
Basicly, DataHandler has two main kind of processings:
Data manipulations -> process_datamap()
Actions (move,delete, copy, translate) -> process_cmdmap()
For DataHandler, you register a class only for datamap and/or processmap, not for a concrete hook itself.
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {}
// <your-ext>/ext_localconf.php
// -> for cmdmap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
// -> for datamap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
You need to register your class only for these kind of hooks you want to consume. And you do not have to implement all hooks.
Hooks can be looked up in \TYPO3\CMS\Core\DataHandling\DataHandler (as hooks are normally searched.
Next step would be to find the proper hook for your use case, and simply add that hook method to your class. Naming the hooks are not chooseable for DataHandler hooks.
TYPO3 Core tests contains a test fixture class for DataHandler hooks - which is not complete, but contains at least the most common ones (along with the needed method signatures) since 8.x:
https://github.com/TYPO3/typo3/blob/main/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php
So you may have to look into the version for your core version to get a feeling how the signature should look for that core version.
Generally I would guess one of these two:
processDatamap_postProcessFieldArray(): Hook with prepared field array, and you can simple add your new stuff to write or update it and it will be saved. Good if you need to change the record directly.
processDatamap_afterDatabaseOperations(): Hook after record has been changed. This is a good startpoint if you need to do other things after saving a record.
Given your usecase, I would tip on the first one, so here a example implementation (in the class and registering as datamap hook as explained above):
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {
/**
* #param string|int $id
*/
public function processDatamap_postProcessFieldArray(
string $status, // Status of the current operation, 'new' or 'update'
string $table, // The table currently processing data for
$id, // The record uid currently processing data for,
// [integer] or [string] (like 'NEW...')
array &$fieldArray, // The field array of a record, cleaned to only
// 'to-be-changed' values. Needs to be &$fieldArray to be considered reference.
DataHandler $dataHandler
): void
{
// $fieldArray may be stripped down to only the real fields which
// needs to be updated, mainly for $status === 'update'. So if you
// need to be sure to have correct data you may have to retrieve
// the record to get the current value, if not provided as with new
// value.
if ($table === 'be_users'
&& $status === 'update'
&& array_key_exists('target_field_name', $fieldArray)
) {
$valueToReactTo = $fieldArray['target_field_name'];
if ($valueToReactTo === 'some-check-value') {
// needs not to be there
$fieldArray['update-field'] = 'my-custom-value';
}
}
}
}

Is there a way to combine Archivable and Versionable behaviours in Propel 1?

I'm using Propel 1 in a fairly sizeable project, and the live version presently uses the Archivable behaviour. Thus, when a row is deleted, the behaviour transparently intercepts the call and moves the row into an archive table. This works fine.
I'm looking to change how this table works so that all saves are versioned. On a feature branch I have therefore removed the Archivable and added the Versionable behaviour. This drops the (table)_archive auto-generated table and adds a (table)_version table instead.
However, interestingly, the version table has a PK of (id, version) with a foreign key to the live table from id to id. This means that versions cannot exist without a live row, which is not what I want: I want to be able to delete a row and preserve the versions.
I thought this behaviour would act like Archivable i.e. the delete() method would be intercepted and modified from its usual approach. Unfortunately, as confirmed by the documentation, this method deletes the live row and any prior versions:
void delete(): Deletes the object version history
I tried mixing both Archivable and Versionable, but this seems to generate code that crashes in the Query API: it tries to call an archive() method that does not exist. I expect this behaviour mix was never intended to work (ideally it should be caught at schema build-time, and perhaps that will be fixed in Propel 2).
One solution is to try the SoftDelete behaviour instead of Archivable - this just marks records as deleted rather than moving them to another table. However this can be problematic because joining to a table with this behaviour can give the wrong counts for non-deleted rows (and the Propel team decided to deprecate it for this reason). It also feels like a rabbit-hole I don't want to go down, since the amount of refactoring may spiral out of control.
Thus, I am left with seeking a better approach to implement a versioning system that does not delete old versions when the live copy is deleted. I can do this manually by intercepting save and delete methods in the model class, but it seems a waste when Versionable nearly does what I want. Are there relevant parameters I can tweak, or is there value in writing a custom behaviour? A quick look at the template generation code for core behaviours makes me want to run away from the latter!
Here is the solution I came up with. My memory is rather hazy but it looks like I've taken the existing VersionableBehaviour and derived from it a new behaviour, which I have called HistoryVersionableBehaviour. It thus uses all the features of the core behaviour and then just overrides the generated delete with its own code.
Here is the behaviour itself:
<?php
// This is how the versionable behaviour works
require_once dirname(__FILE__) . '/HistoryVersionableBehaviorObjectBuilderModifier.php';
class HistoryVersionableBehavior extends VersionableBehavior
{
/**
* Reset the FKs from CASCADE ON DELETE to no action
*
* (I expect all future migration diffs will incorrectly try to re-add the constraint
* I manually removed from the migration that introduced versioning, may try to fix
* that another time. 'Tis fine for now).
*/
public function addVersionTable()
{
parent::addVersionTable();
$this->swapAllForeignKeysToNoDeleteAction();
$this->addVersionArchivedColumn();
}
protected function swapAllForeignKeysToNoDeleteAction()
{
$versionTable = $this->lookupVersionTable();
$fks = $versionTable->getForeignKeys();
foreach ($fks as $fk)
{
$fk->setOnDelete(null);
}
}
protected function addVersionArchivedColumn()
{
$versionTable = $this->lookupVersionTable();
$versionTable->addColumn(array(
'name' => 'archived_at',
'type' => 'timestamp',
));
}
protected function lookupVersionTable()
{
$table = $this->getTable();
$versionTableName = $this->getParameter('version_table') ?
$this->getParameter('version_table') :
($table->getName() . '_version');
$database = $table->getDatabase();
return $database->getTable($versionTableName);
}
/**
* Point to the custom object builder class
*
* #return HistoryVersionableBehaviorObjectBuilderModifier
*/
public function getObjectBuilderModifier()
{
if (is_null($this->objectBuilderModifier)) {
$this->objectBuilderModifier = new HistoryVersionableBehaviorObjectBuilderModifier($this);
}
return $this->objectBuilderModifier;
}
}
This needs something called a modifier, which is run at generation time to produce the base instance classes:
<?php
class HistoryVersionableBehaviorObjectBuilderModifier extends \VersionableBehaviorObjectBuilderModifier
{
/**
* Don't do any version deletion after the main deletion
*
* #param \PHP5ObjectBuilder $builder
*/
public function postDelete(\PHP5ObjectBuilder $builder)
{
$this->builder = $builder;
$script = "// Look up the latest version
\$latestVersion = {$this->getVersionQueryClassName()}::create()->
filterBy{$this->table->getPhpName()}(\$this)->
orderByVersion(\Criteria::DESC)->
findOne(\$con);
\$latestVersion->
setArchivedAt(time())->
save(\$con);
";
return $script;
}
}
The parent class has 798 lines, so my approach does seem to have saved a great deal of code, over building it all from scratch!
You'll need to specify the behaviour in your XML file for each table you want to activate it for:
<table name="job">
<!--- your columns... -->
<behavior name="timestampable" />
<behavior name="history_versionable" />
</table>
I am not sure whether my behaviour requires the presence of the timestampable behaviour - my guess is no, since it looks like the parent behaviour just adds columns to the versioned table and not the table itself. If you are able to try this without the timestampable behaviour do let me know how you get on, so I can update this post.
Finally you'll need to specify the location of your class so the Propel 1 custom autoloader knows where to find it. I use this in my build.properties:
# Declare a custom behaviour
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior

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.

Doctrine returning blank for new field added to database

I have recently added a new field to my database and have added some new getters and setters to my Doctrine model like so:
/**
* #Column(type="text", length=65000, nullable=true)
*/
private $iframeCode;
...
public function getIframeCode(){
return $this->iframeCode;
}
...
public function setIframeCode($val){
$this->iframeCode = $val;
}
This works well locally, and everything is fine. As soon as I upload to the server though, empty strings are always returned whenever I call getIframeCode() on an object, even when I have manually entered values for the field into the database. There are no errors, just empty values returned.
I thought it must be caching (I'm using APC), but I tried using apc_clear_cache() without any luck. Is there something that I'm overlooking?
Thanks.
EDIT:
I have figured out that it IS in fact the caching thats causing this. My question now would be, whats the best and safest way of clearing the cache when you're making a change like this on a production server?

Disable Cakephp's Auto Model "feature"

In cake 1.2 there is a feature that allows the developer to no have to create models, but rather have cake do the detective work at run time and create the model for you. This process happens each time and is neat but in my case very hazardous. I read about this somewhere and now I'm experiencing the bad side of this.
I've created a plugin with all the files and everything appeared to be just great. That is until i tried to use some of the model's associations and functions. Then cake claims that this model i've created doesn't exist. I've narrowed it down to cake using this auto model feature instead of throwing and error! So i have no idea what's wrong!
Does anybody know how to disable this auto model feature? It's a good thought, but I can't seem to find where i've gone wrong with my plugin and an error would be very helpful!
There's always the possibility to actually create the model file and set var $useTable = false.
If this is not what you're asking for and the model and its associations actually do exist, but Cake seems to be unable to find them, you'll have to triple check the names of all models and their class names in both the actual model definition and in the association definitions.
AFAIK you can't disable the auto modelling.
Cake 1.2
It's a hack and it's ugly cus you need to edit core cake files but this is how i do it:
\cake\libs\class_registry.php : line 127ish
if (App::import($type, $plugin . $class)) {
${$class} =& new $class($options);
} elseif ($type === 'Model') {
/* Print out whatever debug info we have then exit */
pr($objects);
die("unable to find class $type, $plugin$class");
/* We don't want to base this on the app model */
${$class} =& new AppModel($options);
}
Cake 2
Costa recommends changing $strict to true in the init function on line 95 of Cake\Utility\ClassRegistry.php
See Cake Api Docs for init
ClassRegistry.php - init function
Use
var $useTable = false;
in your model definition.
Delete all cached files (all files under app/tmp, keep the folders)
In most cases where models seem to be acting in unexpected ways, often they dont include changes you've made, it is because that cake is useing an old cached version of the model.
Uh...where do we start. First, as Alexander suggested, clear your app cache.
If you still get the same behaviour, there is probably something wrong with the class and/or file names.
Remember the rules, for controller:
* classname: BlastsController
* filename: blasts_controller.php
for model:
* classname: Blast
* filename: blast.php
Don't foget to handle the irregular inflections properly.

Categories