Invision Power Board 4.2 relational - php

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

Related

Best way to use class methods in PHP

I wanted to get opinions on the "best" way of using class methods in PHP.
I've always believed this is the better way:
class Customer {
public $customer_id;
public $name;
public $email;
public function __construct( $defaults = array() ) {
foreach ( $defaults as $key => $value ) {
if ( property_exists( $this, $key ) ) {
$this->{ $key } = $value;
}
}
}
public function get( $customer_id ) {
$row = ... // Get customer details from database
if ( $row ) {
$this->customer_id = $customer_id;
$this->name = $row['name'];
$this->email = $row['email'];
else {
throw new Exception( 'Could not find customer' );
}
// Don't return anything, everything is set within $this
}
public function update() {
// No arguments required, everything is contained within $this
if ( ! $this->customer_id ) {
throw new Exception( 'No customer to update' );
}
... // Update customer details in the database using
// $this->customer_id,
// $this->name and
// $this->email
}
}
Methodology #1. The class would then be used like this:
$customer = new Customer();
// Get the customer with ID 123
$customer->get( 123 );
echo 'The customer email address was ' . $customer->email;
// Change the customer's email address
$customer->email = 'abc#zyx.com';
$customer->update();
Methodology #2. However, I see most CodeIgniter examples, and WordPress examples, use this type of methodology:
$customer = new Customer();
// Get the customer with ID 123
$cust = $customer->get( 123 );
echo 'The customer email address was ' . $cust->email;
$cust->email = 'abc#zyx.com';
$customer->update( array( 'customer_id' => 123, 'email' => $cust->email ) );
The second example involves the get method returning a copy of the object, and the update method requires arguments passing in rather than working with $this
I can't help feeling the first example is cleaner and better but perhaps I'm overlooking something?
PS: Please ignore the fact the above examples don't use namespaces, setters, getters, etc, I've trimmed them down to the minimum for illustrative purposes.

MongoDB GridFS Get ID of a file

I am using MongoDB GridFS to store user avatars. I am using Laravel 4.2.
I am writing a function to clean up the DB of any unused avatars; an avatar is unused if it's id is not associated with any user (User model). Given the ID of an avatar I can remove the file from the DB. However I am having trouble extracting ID's of an avatar so I can compare it with those in the User model.
The data looks like this:
$avatars = DB::collection('user_avatars.files')->get();
return Response::json( $avatars[0] );
//result:
{"_id":{"$id":"542797096a8d09ac318b456b"},"extension":"jpg","usage":0,"popularity":[],"filename":"image.jpg","uploadDate":{"sec":1411880713,"usec":671000},"length":248388,"chunkSize":262144,"md5":"2c724361015c7e438d30359dd9c724a0"}
Now if I write:
return Response::json( $avatars[0]['_id'] );
//result is:
{"$id":"542797096a8d09ac318b456b"}
How would I grab 542797096a8d09ac318b456b? Anything I have tried so far does not give the ID but throws an error:
$avatars[0]['_id']->$id;
$avatars[0]['_id']['$id'];
$avatars[0]['_id']->$$id;
$avatars[0]['_id']->{$id};
I tried this and it works great:
avatars[0]['_id']->{'$id'};
Here is the complete function:
public function doPurgeDB()
{
$removed = array();
$avatars = DB::collection('user_avatars.files')->get();
foreach( $avatars as $avatar )
{
$avatar_id = $avatar['_id']->{'$id'}; //<<<<=========
$user = User::where('avatar_id', '=', $avatar_id);
if( !$user->count() )
{
$removed[] = $avatar;
$this->removeAvatar( $avatar_id );
}
}
return Response::json( $removed );
}
public function removeAvatar( $id )
{
$grid = DB::getGridFS('user_avatars');
return $grid->delete( new MongoId( $id ) );
}
Cast it to string: (string)$avatars[0]['_id'];

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.

Rolling back file moves, folder deletes and mysql queries

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.

Categories