I try to get a tier price for a certain customer group in Magento 2.
The tier price should be shown on webshop even if a user is not logged in (and thus not a member of the group at that time)
This tier price is for members only. But to encourage visitors to become a member, the member price and the regular (final) must be visible.
I'm overwriting Magento\Framework\Pricing\Render\RendererPool.
I can get the minimal Tier Price for a configurable product with this group like this:
private function getMemberPrice(Product $product)
{
if ($product->getTypeId() == 'configurable') {
$om = \Magento\Framework\App\ObjectManager::getInstance();
$configurable = $om->get('Magento\ConfigurableProduct\Model\Product\Type\Configurable');
$children = $configurable->getUsedProducts($product);
/**
* #var $childKey
* #var Product $child
*/
$minimalTierMemberPrice = null;
foreach ($children as $childKey => $child) {
$tier_prices = $child->getTierPrices();
if ($tier_prices && count($tier_prices) > 0) {
foreach ($tier_prices as $tier_price) {
if ($tier_price->getCustomerGroupId() == 4 /* MEMBER CUSTOMER GROUP ID */) {
if (!$minimalTierMemberPrice || $minimalTierMemberPrice->getValue() > $tier_price->getValue()) {
$minimalTierMemberPrice = $tier_price;
}
}
}
}
}
if ($minimalTierMemberPrice) {
return $minimalTierMemberPrice;
} else {
return $this->getNonMemberPrice($product);
}
}
//if none member price was set, return the memberprice
return $this->getNonMemberPrice($product);
}
After that I try to override createPriceRender to generate the block with template.
I try to wrap my head around for several days now. That's why I'm trying to push my minimal tier price in the FinalPriceBox / final_price.phtml template as in this code:
public function createPriceRender(
$priceCode,
SaleableInterface $saleableItem,
array $data = []
)
{
if (isset($data['special_price']) && $data['special_price'] == 'member_price') {
$renderClassName = FinalPriceBox::class;
$blockTemplate = 'Magento_Catalog::product/price/final_price.phtml';
$product = $this->getProduct($saleableItem->getId());
$price = $this->getMemberPrice($product);
//$price = $saleableItem->getPriceInfo()->getPrice($priceCode);
//$price = $product->getPriceInfo()->getPrice('final_price');
$arguments['data'] = $data;
$arguments['rendererPool'] = $this;
$arguments['price'] = $price;
$arguments['saleableItem'] = $saleableItem;
//$arguments['member_price'] = true;
/** #var \Magento\Framework\View\Element\Template $renderBlock */
$renderBlock = $this->getLayout()->createBlock($renderClassName, '', $arguments);
if (!$renderBlock instanceof PriceBoxRenderInterface) {
throw new \InvalidArgumentException(
'Block "' . $renderClassName . '" must implement \Magento\Framework\Pricing\Render\PriceBoxRenderInterface'
);
}
$renderBlock->setTemplate($blockTemplate);
return $renderBlock;
} else {
//original code here
return parent::createPriceRender($priceCode, $saleableItem, $data);
}
}
I get following error when I try this
Fatal error: Uncaught TypeError: Argument 3 passed to Magento\Catalog\Pricing\Render\FinalPriceBox\Interceptor::__construct() must implement interface Magento\Framework\Pricing\Price\PriceInterface, instance of Magento\Catalog\Model\Product\TierPrice given
It's quite clear what's the error about. But anyway I want to get my tier price, but with the priceinterface implemented (as the original Rendererpool gets for final prices etc.)
Should I get my tier prices in another way?
Related
In a custom extbase extension, I need to call a show action, passing it another value than uid (this is a continuation of Use other than primary key as RealURL id_field).
As the value "other than uid" is not resolved, it results in the exception http://wiki.typo3.org/Exception/CMS/1297759968
EDIT: here's the current working (but ugly) Controller code:
/**
* ItemController
*/
class ItemController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* itemRepository
*
* #var \STUBR\Weiterbildung\Domain\Repository\ItemRepository
* #inject
*/
protected $itemRepository = NULL;
/**
* action list
*
* #return void
*/
public function listAction(\STUBR\Weiterbildung\Domain\Model\Item $item=null) {
if (!empty(\TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1'))){
$this->forward('show');
}
$items = $this->itemRepository->findAll();
$this->view->assign('items', $items);
}
/**
* action show
*
* #param \STUBR\Weiterbildung\Domain\Model\Item $item
* #return void
*/
public function showAction(\STUBR\Weiterbildung\Domain\Model\Item $item=null) {
$tx_weiterbildung_pi1_get = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1');
if (!empty($tx_weiterbildung_pi1_get)){
$dfid = intval($tx_weiterbildung_pi1_get['durchfuehrungId']);
}
$items = $this->itemRepository->findByDurchfuehrungId($dfid);
// helpful:
//\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($item);
$this->view->assign('item', $items[0]);
}
}
PS here are the 5 lines that could be used in the Repository if the magical method wouldn't work:
//public function findByDurchfuehrungId($dfid) {
// $query = $this->createQuery();
// $query->matching($query->equals('durchfuehrungId', $dfid));
// return $query->execute();
//}
Yes, when you're using actions with model binding in param it will always look for object by field defined as ID - in our case it's always uid, but remember, that you do not need to bind model automatically, as you can retrive it yourself.
Most probably you remember, that some time ago I advised to use <f:link.page additionalParams="{originalUid:myObj.originalUid}"Click here</f:link.page> instead of <f:link.action...>
In that case your show action would look like this:
public function showAction() {
$item = $this->itemRepository->findByOriginalUid(intval(GeneralUtility::_GET('originalUid')));
$this->view->assign('item', $item);
}
Where findByOriginalUid should work magically without declaration, but even if it doesn't it's just matter of 5 lines in the repo ;)
Other sample
According to the code you pasted I'd use it rather like this, in this case listAction gets a role of dispatcher for whole plugin:
public function listAction() {
// access get param in array
$tx_weiterbildung_pi1_get = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1');
$dfId = intval($tx_weiterbildung_pi1_get['durchfuehrungId']);
if ($dfId > 0) { // tx_weiterbildung_pi1 exists and is positive, that means you want to go to showAction
$item = $this->itemRepository->findByDurchfuehrungId($dfId);
if (!is_null($item)) { // Forward to showAction with found $item
$this->forward('show', null, null, array('item' => $item));
}else { // Or redirect to the view URL after excluding single item ID from GET params
$this->redirectToUri($this->uriBuilder->setArgumentsToBeExcludedFromQueryString(array('tx_weiterbildung_pi1'))->build());
}
}
// No `tx_weiterbildung_pi1` param, so it should be displayed as a listAction
$items = $this->itemRepository->findAll();
$this->view->assign('items', $items);
}
/**
* #param \STUBR\Weiterbildung\Domain\Model\Item $item
*/
public function showAction(\STUBR\Weiterbildung\Domain\Model\Item $item = null) {
$this->view->assign('item', $item);
}
Your finder should also getFirst() object if possible:
public function findByDurchfuehrungId($DfId) {
$query = $this->createQuery();
$query->matching($query->equals('durchfuehrungId', $DfId));
return $query->execute()->getFirst();
}
I wonder why no one ever asked this question.
Every zend Action function in controller class has 3 paramters, namely 'module', 'controller', and 'action'.
What happens, when I get a parameter named 'action' from a form or url, for example "?action=edit" ??
I tested it: action holds its value from router, not 'edit'.
public function someAction() {
$params = $this->getRequest()->getParams();
...
How could I pass the parameter named "action", if I had to ??
Thanks in advance.
The default route is Zend_Controller_Router_Route_Module which uses default keys for module, controller, & action:
protected $_moduleKey = 'module';
protected $_controllerKey = 'controller';
protected $_actionKey = 'action';
// ...
/**
* Set request keys based on values in request object
*
* #return void
*/
protected function _setRequestKeys()
{
if (null !== $this->_request) {
$this->_moduleKey = $this->_request->getModuleKey();
$this->_controllerKey = $this->_request->getControllerKey();
$this->_actionKey = $this->_request->getActionKey();
}
if (null !== $this->_dispatcher) {
$this->_defaults += array(
$this->_controllerKey => $this->_dispatcher->getDefaultControllerName(),
$this->_actionKey => $this->_dispatcher->getDefaultAction(),
$this->_moduleKey => $this->_dispatcher->getDefaultModule()
);
}
$this->_keysSet = true;
}
/**
* Matches a user submitted path. Assigns and returns an array of variables
* on a successful match.
*
* If a request object is registered, it uses its setModuleName(),
* setControllerName(), and setActionName() accessors to set those values.
* Always returns the values as an array.
*
* #param string $path Path used to match against this routing map
* #return array An array of assigned values or a false on a mismatch
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$values[$this->_controllerKey] = array_shift($path);
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
You can see that the default module route has default keys for mvc params, however, it will use the keys set by the request object if it exists and we can modify these keys.
e.g. in your bootstrap:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRequestKeys()
{
$this->bootstrap('frontcontroller');
$frontController = $this->getResource('frontcontroller');
/* #var $frontController Zend_Controller_Front */
$request = new Zend_Controller_Request_Http();
// change action key
$request->setActionKey("new_action_key");
// change module
$request->setModuleKey("new_module_key");
// change controller
$request->setControllerKey("new_controller_key");
// don't forget to set the configured request
// object to the front controller
$frontController->setRequest($request);
}
}
Now you can use module, controller, & action as $_GET params.
After a little testing it seems that how you pass the key "action" matters.
If you try and pass a parameter named "action" with $this->_request->getParams() you will get the controller action value key pair.
If you pass the "action" key from a form with $form->getValues() you will retrieve the value from the form element named "action".
As with so many things, your use case determines how you need to handle the situation.
Good Luck.
I want to restrict my users to edit/delete only the comments which they added. I found an example on youtube by a guy named intergral30 and followed his instruction. And now my admin account has the possibility to edit/delete everything, but my user has no access to his own comment.
Here's the code:
Resource
class Application_Model_CommentResource implements Zend_Acl_Resource_Interface{
public $ownerId = null;
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
}
Role
class Application_Model_UserRole implements Zend_Acl_Role_Interface{
public $role = 'guest';
public $id = null;
public function __construct(){
$auth = Zend_Auth::getInstance();
$identity = $auth->getStorage()->read();
$this->id = $identity->id;
$this->role = $identity->role;
}
public function getRoleId(){
return $this->role;
}
}
Assertion
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
if ($user->id != null && $comment->ownerId == $user->id){
return true;
} else {
return false;
}
}
}
In my ACL I have a function named setDynemicPermissions, which is called in an access check plugin's preDispatch method.
public function setDynamicPermissions() {
$this->addResource('comment');
$this->addResource('post');
$this->allow('user', 'comment', 'modify', new Application_Model_CommentAssertion());
$this->allow('admin', 'post', 'modify', new Application_Model_PostAssertion());
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$this->_acl->setDynamicPermissions();
}
And I'm calling the ACL-s isAllowed method from my comment model where I return a list of comment objects.
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
if (Zend_Registry::get('acl')->isAllowed($userRole->getRoleId(), $commentResource->getResourceId(), 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
Can anyone tell me what have I done wrong?
Or what should I use if I want to give my admins the right to start a post and other users the right to comment on them. Each user should have the chance to edit or delete his own comment and an admin should have all rights.
You seem to be using the dynamic assertions in a wrong manner, as you are still passing the roleId to isAllowed().
What these dynamic assertions really do, is take a complete object and work with it. Zend will determine which rule has to be used by calling getResourceId() and getRoleId() on your objects.
So all you have to do is pass your objects instead of the strings to isAllowed():
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// This line includes the changes
if (Zend_Registry::get('acl')->isAllowed($userRole, $commentResource, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
But in can be done better
You would not have to implement a total new Application_Model_CommentResource, but instead you can use your actual Application_Model_Comment like this:
// we are using your normal Comment class here
class Application_Model_Comment implements Zend_Acl_Resource_Interface {
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
// all other methods you have implemented
// I think there is something like this among them
public function getOwnerId() {
return $this->ownerId;
}
}
Assertion would then use this object and retrieve the owner to compare it with the actually logged in person:
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface {
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
// using the method now instead of ->ownerId, but this totally depends
// on how one can get the owner in Application_Model_Comment
if ($user->id != null && $comment->getOwnerId() == $user->id){
return true;
} else {
return false;
}
}
And the usage is like this:
public function getComments($id) {
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$comments = array();
foreach ($res as $comment) {
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// no $commentResource anymore, just pure $comment
if (Zend_Registry::get('acl')->isAllowed($userRole, $comment, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
There is a multiselect element in form. It's needed to validate how many items are selected in it (min and max count).
The trouble is that when the element can have multiple values, then each value is validated separately.
I tried to set isArray to false to validate the value with my custom validator ArraySize, but new problem appeared: the whole array-value is passed to InArray validator and the validation fails. So I had to turn it off by setting registerInArrayValidator to false.
Now I can validate the value for number of selected values but can not validate for their correspondence to provided options.
Is there a way to solve the problem without creating one more custom validator?
Note: I have assume this is Zend 1.
The only way I could see to do this was to extend Multiselect and use a custom isValid. This way you can see the full array of values and not just one value at a time.
Below is my custom Multiselect class
<?php
/**
* My_MinMaxMultiselect
*/
class My_MinMaxMultiselect extends Zend_Form_Element_Multiselect
{
/**
* Validate element contains the correct number of
* selected items. Check value against minValue/maxValue
*
* #param mixed $value
* #param mixed $context
* #return boolean
*/
public function isValid($value, $context = null)
{
// Call Parent first to cause chain and setValue
$parentValid = parent::isValid($value, $context);
$valid = true;
if ((('' === $value) || (null === $value))
&& !$this->isRequired()
&& $this->getAllowEmpty()
) {
return $valid;
}
// Get All Values
$minValue = $this->getMinValue();
$maxValue = $this->getMaxValue();
$count = 0;
if (is_array($value)) {
$count = count($value);
}
if ($minValue && $count < $minValue) {
$valid = false;
$this->addError('The number of selected items must be greater than or equal to ' . $minValue);
}
if ($maxValue && $count > $maxValue) {
$valid = false;
$this->addError('The number of selected items must be less than or equal to ' . $maxValue);
}
return ($parentValid && $valid);
}
/**
* Get the Minimum number of selected values
*
* #access public
* #return int
*/
public function getMinValue()
{
return $this->getAttrib('min_value');
}
/**
* Get the Maximum number of selected values
*
* #access public
* #return int
*/
public function getMaxValue()
{
return $this->getAttrib('max_value');
}
/**
* Set the Minimum number of selected Values
*
* #param int $minValue
* #return $this
* #throws Bad_Exception
* #throws Zend_Form_Exception
*/
public function setMinValue($minValue)
{
if (is_int($minValue)) {
if ($minValue > 0) {
$this->setAttrib('min_value', $minValue);
}
return $this;
} else {
throw new Bad_Exception ('Invalid value supplied to setMinValue');
}
}
/**
* Set the Maximum number of selected values
*
* #param int $maxValue
* #return $this
* #throws Bad_Exception
* #throws Zend_Form_Exception
*/
public function setMaxValue($maxValue)
{
if (is_int($maxValue)) {
if ($maxValue > 0) {
$this->setAttrib('max_value', $maxValue);
}
return $this;
} else {
throw new Bad_Exception ('Invalid value supplied to setMaxValue');
}
}
/**
* Retrieve error messages and perform translation and value substitution.
* Overridden to avoid errors from above being output once per value
*
* #return array
*/
protected function _getErrorMessages()
{
$translator = $this->getTranslator();
$messages = $this->getErrorMessages();
$value = $this->getValue();
foreach ($messages as $key => $message) {
if (null !== $translator) {
$message = $translator->translate($message);
}
if (($this->isArray() || is_array($value))
&& !empty($value)
) {
$aggregateMessages = array();
foreach ($value as $val) {
$aggregateMessages[] = str_replace('%value%', $val, $message);
}
// Add additional array unique to avoid the same error for all values
$messages[$key] = implode($this->getErrorMessageSeparator(), array_unique($aggregateMessages));
} else {
$messages[$key] = str_replace('%value%', $value, $message);
}
}
return $messages;
}
}
To use this in the form where the User must select exactly 3 options:
$favouriteSports = new MinMaxMultiselect('favourite_sports');
$favouriteSports
->addMultiOptions(array(
'Football',
'Cricket',
'Golf',
'Squash',
'Rugby'
))
->setRequired()
->setLabel('Favourite Sports')
->setMinValue(3)
->setMaxValue(3);
While it's nice when you can squeeze by without needing to write a custom validator, there is nothing wrong with writing one when you need to do something slightly out of the ordinary.
This sounds like one of those cases.
I have a registration form with different input fields one of them being a multi checkbox so that the user can decide what countries he wants to receive information from. This last one is created like this:
$pais = $this->createElement('multiCheckbox', 'pais');
$pais->setLabel('Pais\es: ');
$pais->addMultioption('1', 'Argentina');
$pais->addMultioption('2', 'Espa?a');
$pais->addMultioption('3', 'Brasil');
$pais->addMultioption('4', 'USA');
$pais->addMultioption('5', 'Italia');
$this->addElement($pais);
In my UserController I have the following action to update the table 'users':
public function createAction()
{
$this->view->pageTitle = 'Create User';
require_once APPLICATION_PATH . '/models/Users.php';
$userForm = new Form_User();
if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
$userModel = new Model_User();
$userMode->createUser(
$userForm->getValue('email'),
$userForm->getValue('password'),
$userForm->getValue('url'),
$userForm->getValue('responsable'),
$userForm->getValue('role')
);
return $this->_forward('list');
}
}
$userForm->setAction('/user/create');
$this->view->form = $userForm;
}
which of course, right now is not contemplating the multicheckbox populatedn$pais variable, nor here nor in the model:
class Model_User extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
public function createUser($email, $password, $url, $responsable, $role)
{
// create a new row
$rowUser = $this->createRow();
if($rowUser) {
// update the row values
$rowUser->email = $email;
$rowUser->password = md5($password);
$rowUser->url = $url;
$rowUser->responsable = $responsable;
$rowUser->role = $role;
$rowUser->save();
//return the new user
return $rowUser;
} else {
throw new Zend_Exception("El usuario no se ha podido crear!");
}
}
}
I have also a 'pais' table, which contains the 5 different countries, and I'm working on a separate model for 'users_has_pais' which is the table I created in the workbench for this purpose...but I'm not getting any results with what I'm doing right now. Can someone point me in the right path to get to update 'users_has_pais' at the same time that I update the 'users' table?
Thanks a lot in advance to anyone with good advice on this.
EDIT: this is the db model in case anyone needs it to figure out what I'm saying
EDIT2:
public function createAction()
{
$this->view->pageTitle = 'Create User';
require_once APPLICATION_PATH . '/models/Users.php';
$userForm = new Form_User();
if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
$userModel = new Model_User();
$user = $userModel->createUser(
$userForm->getValue('email'),
$userForm->getValue('password'),
$userForm->getValue('url'),
$userForm->getValue('responsable'),
$userForm->getValue('role')
);
$paises = $this->getRequest()->getParam('pais');
$userId = intval($user['id']);
require_once APPLICATION_PATH . '/models/UserHasPais.php';
$paisesModel = new Model_UsersHasPais();
$paisesModel->updateUserPais($userId, $paises);
return $this->_forward('index');
}
}
and users_has_pais model:
class Model_UsersHasPais extends Zend_Db_Table_Abstract
{
protected $_name = 'users_has_pais';
public function updateUserPais($id, array $paises)
{
$row = ($r = $this->fetchRow(array('users_id = ?' => $id))) ? $r : $this->createRow();
foreach($paises as $pais){
$row->users_id = $id;
$row->pais_id = $pais;
$row->save();
}
}
}
One way would be to create user row first, and use it's ID when creating rows for 'user_has_pais'. A pseudo-code is below:
public function createAction()
{
$this->view->pageTitle = 'Create User';
require_once APPLICATION_PATH . '/models/Users.php';
$userForm = new Form_User();
if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
$userModel = new Model_User();
$newUserRow = $userMode->createUser(
$userForm->getValue('email'),
$userForm->getValue('password'),
$userForm->getValue('url'),
$userForm->getValue('responsable'),
$userForm->getValue('role')
);
$user_id = newUserRow->id;
$checkBoxValues = $userForm->getValue('pais');
// $checkBoxValues should be an array where keys are option names and
// values are values. If checkbox is not checked, than the value = 0;
// At this moment I'm not 100% sure of the real nature of the 'pais' value,
// but this is only an example.
// I also assume that the values of the checkboxfields correspond to IDs in
// 'pais'.
foreach ($checkBoxValues as $key => $pais_id) {
if (intval(pais_id) > 0) {
// if language was checked
// do insert into user_has_pais having $pais_id and $user_id.
}
}
return $this->_forward('list');
}
}
$userForm->setAction('/user/create');
$this->view->form = $userForm;
}
You could also put all of this in transaction if you want.
Hope this helps, or at least you point you in the right direction.