Rolling back file moves, folder deletes and mysql queries - php

This has been bugging me all day and there is no end in sight.
When the user of my php application adds a new update and something goes wrong, I need to be able to undo a complex batch of mixed commands. They can be mysql update and insert queries, file deletes and folder renaming and creations.
I can track the status of all insert commands and undo them if an error is thrown.
But how do I do this with the update statements?
Is there a smart way (some design pattern?) to keep track of such changes both in the file structure and the database?
My database tables are MyISAM. It would be easy to just convert everything to InnoDB, so that I can use transactions. That way I would only have to deal with the file and folder operations.
Unfortunately, I cannot assume that all clients have InnoDB support. It would also require me to convert many tables in my database to InnoDB, which I am hesitant to do.

PDO's rowcount() returns eftected rows on updates. mysqli's afftected_rows does the same
I'm by clients you mean clients whose servers you will be placing this application on. If you weren't to require innoDB on the servers you'd have to do some more coding to rollback changes on MyISAM tables.
The best way would be to modularize everything into functions (or class methods)
pseudo code:
function updateThisThing() {
if ( !updateTable() ) {
rollbackUpdateTable();
return false;
}
if ( !updateFiles() ) {
rollbackUpdateFiles();
return false;
}
// more update statements
return true
}

If you're absolutely stuck with MyISAM, you should see if the code can be arranged so that UPDATES are the very last thing performed. If an error occurs before then, no UPDATEs will be made.
If that's not feasible, you'll have to lock the pertinent tables, grab the current records, update them. If error, restore with grabbed records. Unlock tables.
Not very practical which is why there's InnoDB (as you know).
I think that's the basis of this module which you can check out:
http://www.deepbluesky.com/blog/-/myisam-transactions_20/

Have you looked into the Unit of Work pattern?
Here's a really roughshod example of how you might get started.
The basic UnitOfWork container.
class UnitOfWork
{
protected $entities = array();
protected $completed = array();
final public function addEntity( IWorkUnitEntity $entity )
{
$this->entities[] = $entity;
}
final public function execute()
{
try {
foreach ( $this->entities as $entity )
{
$entity->execute();
$completed[] = $entity;
}
}
catch ( UnitOfWorkRollbackException $e )
{
$this->rollbackCompleted();
}
return $this->commitAll();
}
protected function rollbackCompleted()
{
while ( $entity = array_pop( $this->completed ) )
{
$entity->rollback();
}
}
protected function commitAll()
{
try {
foreach ( $this->entities as $entity )
{
$entity->commit();
}
}
catch ( UnitOfWorkRollbackException $e )
{
$this->rollbackCompleted();
return false;
}
return true;
}
}
A couple extras to help it along
class UnitOfWorkRollbackException extends Exception {};
interface IWorkUnitEntity
{
public function execute();
public function rollback();
}
Now, an example of a work entity
class FileMoverEntity implements IWorkUnitEntity
{
protected
$source
, $destination
, $newName
;
public function __construct( $source, $destination, $newName = null )
{
$this->source = $source;
$this->destination = dirname( $destination );
$this->newName = $newName;
}
public function execute()
{
if ( is_readable( $this->source ) && is_writable( $this->destination ) )
{
return true;
}
throw new UnitOfWorkRollbackException( 'File cannot be moved' );
}
public function commit()
{
$filename = ( null === $this->newName )
? basename( $this->source )
: $this->newName
;
if ( !rename( $this->source, $this->destination . DIRECTORY_SEPARATOR . $filename ) )
{
throw new UnitOfWorkRollbackException( 'File move failed' );
}
}
public function rollback()
{
// Nothing to do here since the file doesn't actually move until commit()
}
}
Putting it all together.
$UoW = new UnitOfWork();
$UoW->addEntity( new FileMoverEntity( '/tmp/foo', '/home/me', 'profile.jpg' ) );
$UoW->addEntity( new FileMoverEntity( '/tmp/bar', '/root', 'profile.jpg' ) );
if ( $UoW->execute() )
{
// all operations successful
}
Now, I didn't do some things you'd want to here - like keeping track of which exceptions were thrown so the client script can access that info - but I think you get the idea. And of course you can go on to make work entities for all sorts of operations - DB updates, API calls, whatever.
In terms of connecting to a database without transaction-safe tables - I don't have any insight.

Related

Invision Power Board 4.2 relational

Im currently trying to get a relational field from ipb but don't know how. Im currently utilizing the pages application and have a field that uses a Database relation. This is the current function that gets the relational fields:
public function getReciprocalItems()
{
/* Check to see if any fields are linking to this database in this easy to use method wot I writted myself */
if ( \IPS\cms\Databases::hasReciprocalLinking( static::database()->_id ) )
{
$return = array();
/* Oh that's just lovely then. Lets be a good fellow and fetch the items then! */
foreach( \IPS\Db::i()->select( '*', 'cms_database_fields_reciprocal_map', array( 'map_foreign_database_id=? and map_foreign_item_id=?', static::database()->_id, $this->primary_id_field ) ) as $record )
{
try
{
$recordClass = 'IPS\cms\Records' . $record['map_origin_database_id'];
$return[ $record['map_field_id'] ][] = $recordClass::load( $record['map_origin_item_id'] );
}
catch ( \Exception $ex ) { }
}
/* Has something gone all kinds of wonky? */
if ( ! count( $return ) )
{
return FALSE;
}
return $return;
}
return FALSE;
}
the code ipb uses for display is kind of like twig

PHP: Unable to call a public function

I have a page dashboard.php, which creates a merchant dashboard that shows deals submitted by the merchant. I'm simply trying to separate types of deals by checking to see if a deal is a suggested deal:
...
while ($deals->have_posts()) : $deals->the_post();
$suggested_deal = SA_Post_Type::get_instance( $post->ID );
$boolsuggesteddeal = $suggested_deal->is_suggested_deal();
...
However, the is_suggested_deal() line is causing the page to not display anything past that line.
The SA_POST_TYPE class is outlined below:
class SA_Post_Type extends Group_Buying_Deal {
...
public static function get_instance( $id = 0 ) {
if ( !$id ) {
return NULL;
}
if ( !isset( self::$instances[$id] ) || !self::$instances[$id] instanceof self ) {
self::$instances[$id] = new self( $id );
}
if ( self::$instances[$id]->post->post_type != parent::POST_TYPE ) {
return NULL;
}
return self::$instances[$id];
}
...
public function is_suggested_deal() {
$term = array_pop( wp_get_object_terms( $this->get_id(), self::TAX ) );
return $term->slug == self::TERM_SLUG;
}
...
Since the class and function are both public, why am I unable to call the function? Any help would be greatly appreciated.
EDIT: I can't figure out how to get error reporting on without showing all site users the errors, I'm on a live site. I tried creating an instance of SA_Post_Type(), but that alone cause the page to fail to load anything after that line.
You have not created an instance of the class, do so like this...
$SA_Post_Type = new SA_Post_Type();
Then you are able to access the function...
$boolsuggesteddeal = $SA_Post_Type->is_suggested_deal();
Since is_suggested_deal is not a static function, you have to create a new instance of the SA_Post_Type class firstly.
$sa_post_type = new SA_Post_Type();
$boolsuggesteddeal = $sa_post_type->is_suggested_deal();
Hope this helps.

How to dynamically change table schemas for multi-tenant databases in Doctrine 2/PHP 5.4

WHAT I'M TRYING TO DO:
So I'm trying to implement a multi-tenant database architecture using SQL Azure, PHP 5.4, Zend Framework 2, and Doctrine 2. I'm going with the "Shared Database, Separate Schemas" architecture as mentioned in this article: http://msdn.microsoft.com/en-us/library/aa479086.aspx
Unlike simple multi-tenant environments, my environment has certain use cases where a User from Tenant A should be able to access information from a table in Tenant B. So because of this there are "root" or "global" tables that aren't made for each tenant and are instead used by all tenants. So as an example, I could have a table called users that exists for each tenant each with a unique schema name (e.g. tenanta.users and tenantb.users). I would then also have a root schema for things like global permissions (e.g. root.user_permissions).
WHAT I'VE DONE:
In Module.php's onBootstrap() function I've set up a loadClassMetadata event for dynamically changing the schemas of tables, like so:
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default')->getEventManager();
$entityManager->addEventListener(array( \Doctrine\ORM\Events::loadClassMetadata ), new PrependTableEvent() );
The PrependTableEvent object uses session data to know which schema to use, it looks like so:
namespace Application\Model;
use Zend\Session\Container;
class PrependTableEvent {
private $session;
public function __construct() {
$this->session = new Container('base');
}
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) {
$classMetadata = $eventArgs->getClassMetadata();
$table = $classMetadata->table;
$table_name = explode('.', $table['name']);
if ( 'root' != $table_name[0] && NULL !== $this->session->queryschema ) {
$table['name'] = $this->session->queryschema . '.' . $table_name[1];
}
$classMetadata->setPrimaryTable($table);
}
}
In order for loadClassMetadata to be called everytime the queryschema changes I built a QuerySchemaManager that looks like so:
namespace Application\Model;
use Doctrine\ORM\Events,
Doctrine\ORM\Event\LoadClassMetadataEventArgs,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\EntityManager,
Zend\Session\Container;
class QuerySchemaManager {
private static $session;
private static $initialized = FALSE;
private static function initialize() {
QuerySchemaManager::$session = new Container('base');
QuerySchemaManager::$initialized = TRUE;
}
public static function reload_table_name( EntityManager $em, $class, $schema) {
if ( ! QuerySchemaManager::$initialized ) {
QuerySchemaManager::initialize();
}
QuerySchemaManager::$session->queryschema = $schema;
if ($em->getEventManager()->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new LoadClassMetadataEventArgs($em->getClassMetadata($class), $em);
$em->getEventManager()->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
}
public static function reload_all_table_names( EntityManager $em, $schema) {
if ( ! QuerySchemaManager::$initialized ) {
QuerySchemaManager::initialize();
}
QuerySchemaManager::$session->queryschema = $schema;
if ($em->getEventManager()->hasListeners(Events::loadClassMetadata)) {
$metadatas = $em->getMetadataFactory()->getAllMetadata();
foreach($metadatas as $metadata) {
$eventArgs = new LoadClassMetadataEventArgs($metadata, $em);
$em->getEventManager()->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
}
}
}
All that code works great and properly updates the ClassMetadata files for each entity.
THE PROBLEM:
I have an issue with Doctrine 2 where when I insert values into a table for Tenant A and then try to insert values into the same table for Tenant B, all the rows get inserted into Tenant A's table.
I spent a lot of time following break points to find the problem, but I still have no idea how to solve it.
(Note: All the following Code is from Doctrine, so it can't/shouldn't be edited by me)
The problem is that EntityManager->unitOfWork has a private array called $persisters that stores (in my case) BasicEntityPersister objects. Every time one of the BasicEntityPersisters are needed UnitOfWork's getEntityPersister($entityName) is called which looks like so:
public function getEntityPersister($entityName)
{
if ( ! isset($this->persisters[$entityName])) {
$class = $this->em->getClassMetadata($entityName);
if ($class->isInheritanceTypeNone()) {
$persister = new Persisters\BasicEntityPersister($this->em, $class);
} else if ($class->isInheritanceTypeSingleTable()) {
$persister = new Persisters\SingleTablePersister($this->em, $class);
} else if ($class->isInheritanceTypeJoined()) {
$persister = new Persisters\JoinedSubclassPersister($this->em, $class);
} else {
$persister = new Persisters\UnionSubclassPersister($this->em, $class);
}
$this->persisters[$entityName] = $persister;
}
return $this->persisters[$entityName];
}
So it will create one BasicEntityPersister per entity (i.e. Application\Model\User will have one BasicEntityPersister even though its schema name will dynamically change), which is fine.
Each BasicEntityPersister has a private member called $insertSql which stores the insert SQL statement once it has been created. When the insert statement is needed this method is called:
protected function getInsertSQL()
{
if ($this->insertSql !== null) {
return $this->insertSql;
}
$columns = $this->getInsertColumnList();
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
if (empty($columns)) {
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
return $this->insertSql;
}
$values = array();
$columns = array_unique($columns);
foreach ($columns as $column) {
$placeholder = '?';
if (isset($this->class->fieldNames[$column])
&& isset($this->columnTypes[$this->class->fieldNames[$column]])
&& isset($this->class->fieldMappings[$this->class->fieldNames[$column]]['requireSQLConversion'])) {
$type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
}
$values[] = $placeholder;
}
$columns = implode(', ', $columns);
$values = implode(', ', $values);
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
return $this->insertSql;
}
These three lines are the culprit:
if ($this->insertSql !== null) {
return $this->insertSql;
}
If those lines were commented out then it would work perfectly as the metadata it uses to create the insertSql statement updates properly. I can't find a way to delete/overwrite the insertSql variable, or to even delete/overwrite the whole BasicEntityPersister.
Anyone who's implemented a multi-tenant environment using Doctrine 2 I would like to know how you did it. I don't mind redoing all or large parts of my work, I just need to know what the best way to go about doing this is. Thanks in advance.
You are fighting against the ORM (which is meant to be generating the SQL for you). This should be a signal that you perhaps are going about things in the wrong way.
Rather than modify the persisters (that generate the SQL strings) you should be adding an additional EntityManager. Each entity EntityManager (and therefore UnitOfWork) are designed to persist to one schema; so your second one would simple handle the persistence to the second database - No need to change Doctrine internals!
I have not personally tried to connect to two schemas; however reading into it it seems that it should be possible with the DoctrineModule v1.0.

Laravel Eloquent many-to-many attach

I am new to laravel, and trying to build a photo album with it.
My problem is that i use the attach function to insert the user id and group id to my database, it works okay, but in the documentation it says this about the attach function
For example, perhaps the role you wish to attach to the user already
exists. Just use the attach method:
So i wanted to use it the same way, if the album_id already exist just update it, other wise insert thr new one, but my problem is it always insters, it does not checks if the album_id already exsits
My model
class User extends Eloquent
{
public static $timestamps = false;
public function album()
{
return $this->has_many_and_belongs_to('album', 'users_album');
}
}
Post function
public function post_albums()
{
$user = User::find($this->id);
$album_id = Input::get('album');
$path = 'addons/uploads/albums/'.$this->id.'/'. $album_id . '/';
$folders = array('path' => $path, 'small' => $path. 'small/', 'medium' => $path. 'medium/', );
if (! is_dir($path) )
{
foreach ($folders as $folder)
{
#mkdir($folder, 0, true);
}
}
$sizes = array(
array(50 , 50 , 'crop', $folders['small'], 90 ),
array(164 , 200 , 'crop', $folders['medium'], 90 ),
);
$upload = Multup::open('photos', 'image|max:3000|mimes:jpg,gif,png', $path)
->sizes( $sizes )
->upload();
if($upload)
{
$user->album()->attach($album_id);
return Redirect::back();
}
else
{
// error show message remove folder
}
}
Could please someone point out what im doing wrong? Or i totally misunderstod the attach function?
I believe you have misunderstood the attach function. The sync function uses attach to add relationships but only if the relationship doesn't already exist. Following what was done there, i'd suggest pulling a list of id's then only inserting if it doesn't already exist in the list.
$current = $user->album()->lists( 'album_id' );
if ( !in_array( $album_id, $current ) )
{
$user->album()->attach( $album_id );
}
On a side note I'm going to suggest that you follow the default naming convention from laravel. The relationship method should be $user->albums() because there are many of them. The pivot table should also be named 'album_user'. You will thank yourself later.
Contains method of Laravel Collections
The laravel collections provides a very useful method 'contains'. It determine if a key exists in the collection. You can get the collection in your case using $user->album. You can note the difference that album is without paranthesis.
Working code
Now all you had to do is use the contains method. The full code will be.
if (!$user->album->contains($album_id)
{
$user->album()->attach($album_id);
}
Its more cleaner and 'Laravel' way of getting the required solution.
Thanks #Collin i noticed i misunderstand i made my check yesterday
$album = $user->album()->where_album_id($album_id)->get();
if(empty($album))
{
$user->album()->attach($album_id);
}

Implementing not automatic badges with PHP and MYSQL

I have users' table users, where I store information like post_count and so on. I want to have ~50 badges and it is going to be even more than that in future.
So, I want to have a page where member of website could go and take the badge, not automatically give him it like in SO. And after he clicks a button called smth like "Take 'Made 10 posts' badge" the system checks if he has posted 10 posts and doesn't have this badge already, and if it's ok, give him the badge and insert into the new table the badge's id and user_id that member couldn't take it twice.
But I have so many badges, so do I really need to put so many if's to check for all badges? What would be your suggestion on this? How can I make it more optimal if it's even possible?
Thank you.
optimal would be IMHO the the following:
have an object for the user with functions that return user specific attributes/metrics that you initialise with the proper user id (you probably wanna make this a singleton/static for some elements...):
<?
class User {
public function initUser($id) {
/* initialise the user. maby load all metrics now, or if they
are intensive on demand when the functions are called.
you can cache them in a class variable*/
}
public function getPostCount() {
// return number of posts
}
public function getRegisterDate() {
// return register date
}
public function getNumberOfLogins() {
// return the number of logins the user has made over time
}
}
?>
have a badge object that is initialised with an id/key and loads dependencies from your database:
<?
class Badge {
protected $dependencies = array();
public function initBadge($id) {
$this->loadDependencies($id);
}
protected function loadDependencies() {
// load data from mysql and store it into dependencies like so:
$dependencies = array(array(
'value' => 300,
'type' => 'PostCount',
'compare => 'greater',
),...);
$this->dependencies = $dependencies;
}
public function getDependencies() {
return $this->dependencies;
}
}
?>
then you could have a class that controls the awarding of batches (you can also do it inside user...)
and checks dependencies and prints failed dependencies etc...
<?
class BadgeAwarder {
protected $badge = null;
protected $user = null;
public function awardBadge($userid,$badge) {
if(is_null($this->badge)) {
$this->badge = new Badge; // or something else for strange freaky badges, passed by $badge
}
$this->badge->initBadge($badge);
if(is_null($this->user)) {
$this->user = new User;
$this->user->initUser($userid);
}
$allowed = $this->checkDependencies();
if($allowed === true) {
// grant badge, print congratulations
} else if(is_array($failed)) {
// sorry, you failed tu full fill thef ollowing dependencies: print_r($failed);
} else {
echo "error?";
}
}
protected function checkDependencies() {
$failed = array();
foreach($this->badge->getDependencies() as $depdency) {
$value = call_user_func(array($this->badge, 'get'.$depdency['type']));
if(!$this->compare($value,$depdency['value'],$dependency['compare'])) {
$failed[] = $dependency;
}
}
if(count($failed) > 0) {
return $failed;
} else {
return true;
}
}
protected function compare($val1,$val2,$operator) {
if($operator == 'greater') {
return ($val1 > $val2);
}
}
}
?>
you can extend to this class if you have very custom batches that require weird calculations.
hope i brought you on the right track.
untested andp robably full of syntax errors.
welcome to the world of object oriented programming. still wanna do this?
Maybe throw the information into a table and check against that? If it's based on the number of posts, have fields for badge_name and post_count and check that way?

Categories