Session Handling and Object Serialization


Last night I worked on FLOW3's Object Serializer - it didn't detect recursions in the references of the object tree to be serialized. That's a good opportunity for me to give some insight into FLOW3's session and object serialization capabilities.

In a plain PHP script it's quite easy to store data in a session - all you need to do is starting a session with session_start() and store any data in the super-global $_SESSION. There are, however, limitations to this approach. Circular references in the object tree can become a problem and mechanisms like Dependency Injection or the special approach we take for persisting objects are, of course, not supported by PHP's built-in functions.

Consider the following domain model:

/**
 * @scope prototype
 */
class Order {

	/**
	 * @var integer
	 */
	protected $orderNumber;
	
	/**
	 * @var \F3\MyApp\Domain\Model\Recipient
	 */
	protected $recipient;

	/**
	 * @var \F3\MyApp\Domain\Model\Article
	 */
	protected $article;

	/**
	 * @inject
	 * @var \F3\MyApp\Domain\Service\OrderNumberGeneratorService
	 */
	protected $orderNumberGenerator;
	
	/**
	 * Constructs a new order
	 */
	public function __construct() {
		$this->orderNumber = $this->orderNumberGenerator->generate();
	}
	
	/**
	 * @param \F3\MyApp\Domain\Model\Recipient $recipient
	 * @return void
	 */
	public function setRecipient(\F3\MyApp\Domain\Model\Recipient $recipient) {
		$recipient->addOrder($this);
		$this->recipient = $recipient;
	}
	...
}

There are some challenges you need to take in order to store an instance of this Order class in your session:

  • the OrderNumberGeneratorService is a (singleton-scoped) service, injected by the object manager and thus doesn't need to and should not be stored in the session
  • on reconstituting data from the session on the next request, we need to re-inject all dependencies which were not persisted
  • the Order has a reference to the Recipient which in turn has (for demo purposes) a reference to all its orders - a circular reference
  • the Article should not be stored in the session but instead FLOW3 should store the uuid of the already persisted article
  • because FLOW3 manages objects centrally, almost all instances are somehow connected to eachother - trying to serialize one of them would serialize all objects in the system

FLOW3 solves these challenges for you - all you need to do is attaching the Order to some object which has a session scope:

/**
 * @scope session
 */
class ShoppingSession {

	/**
	 * @var \F3\MyApp\Domain\Model\Order
	 */
	protected $order;

	/**
	 * @param \F3\MyApp\Domain\Model\Order $order
	 */
	public function setOrder(\F3\MyApp\Domain\Model\Order $order) {
		$this->order = $order;
	}
}

Storing the Order in the ShoppingSession is now a matter of two lines of code:

$shoppingSession = $this->objectManager->get('F3\MyApp\Domain\Service\ShoppingSession');
$shoppingSession->setOrder($order);

Of course you can also use Dependency Injection for the retrieval of the ShoppingSession. At the end of the request, FLOW3 analyzes the data attached to session objects. The ObjectSerializer, which is in charge of serializing the object trees, detects that theOrderNumberGeneratorService is of scope Singleton and has been injected - therefore it's omitted during the serialization. At the beginning of the next request, FLOW3 makes sure to re-inject all dependencies which have not been stored in the session data. Because or recipient is declared as an @entity and already exists in the RecipientRepository (i.e. it's not new and has not been modified during the request), it's sufficient to store the object's UUID instead of the whole data. FLOW3's ObjectSerializer automatically creates a reference to the persisted Recipient object. As a consequence, changes to the Recipient data made by someone else will be visible on the next request, because our session doesn't contain the actual Recipient data - only a reference.

Finally if you want to explicitly omit the serialization of certain properties which otherwise would be stored in the session, you can tag them by a "@transient" annotation:

/**
 * @var sometype
 * @transient
 */
protected $propertyWhichShouldNotBeStored;

Anything else you'd like to see in FLOW3's session handling?


Leave a reply