Magento 2: Before & After Method Observer using Plugin - magento

I want to write a observer for before and after methods using plugin in magento 2.

The plugin class has to be declared in etc/di.xml of your module. Here is the code:
app/code/YourCompany/YourModule/etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Checkout\Model\Cart">
<plugin name="pluginAddProductToCart" type="YourCompany\YourModule\Plugin\CartPlugin" sortOrder="10" disabled="false"/>
</type>
</config>
Here,
type’s name = class whose methods are to be observed
plugin’s name = random plugin name
plugin’s type = name of plugin’s class (YourCompany\YourModule\Plugin\Plugin)
plugin’s sortOrder = the order of the plugin to be called
plugin’s disabled = enable/disable plugin, default value is false.
We can add before, after, and around methods to modify the core class functions. For example, if there is a “Save” function in the core class then in the plugin class we can have beforeSave, afterSave and aroundSave method.
beforeMethod = contains code to run before the observed Method
afterMethod = contains code to run after the observed Method
aroundMethod = contains code to run both before and after the observed Method
“observed method” means the function present in the Core class which we want to modify through our Plugin class
In the above di.xml file, we have defined that we are going to observe methods of class Magento\Checkout\Model\Cart in our plugin class YourCompany\YourModule\Plugin\CartPlugin.
In this example, we will be observing a function named addProduct of the core Cart class.
For this example,
If you want to run some code before the addProduct method (adding product to cart), then you will have to create a method named beforeAddProduct in your Plugin class.
Similarly, if you want to do something after adding product to cart, then you will have to create a new function named afterAddProduct in your Plugin class.
And, there is another method named ‘around’. This allows you to execute some code both before and after the observed method is called. For this, you will have to add a new function named aroundAddProduct in your Plugin class.
Here is the CartPlugin class.
app/code/YourCompany/YourModule/Plugin/CartPlugin.php
<?php
namespace YourCompany\YourModule\Plugin\CartPlugin;
use Magento\Framework\Exception\LocalizedException;
class CartPlugin
{
/**
* #var \Magento\Quote\Model\Quote
*/
protected $quote;
protected $request;
/**
* Plugin constructor.
*
* #param \Magento\Checkout\Model\Session $checkoutSession
*/
public function __construct(
\Magento\Checkout\Model\Session $checkoutSession,
\Magento\Framework\App\Request\Http $request
) {
$this->quote = $checkoutSession->getQuote();
$this->request = $request;
}
/**
* beforeAddProduct
*
* #param $subject
* #param $productInfo
* #param null $requestInfo
*
* #return array
* #throws LocalizedException
*/
public function beforeAddProduct($subject, $productInfo, $requestInfo = null)
{
$productId = (int)$this->request->getParam('product', 0);
$qty = (int)$this->request->getParam('qty', 1);
// do something
// your code goes here
if ( something wrong ) {
throw new LocalizedException(__('Your error message'));
}
return [$productInfo, $requestInfo];
}
/**
* afterAddProduct
*
* #param $subject
* #param $result Returned value from core observed method 'addProduct'
*/
public function afterAddProduct($subject, $result)
{
$quote = $result->getQuote();
// do something
// your code goes here
}
public function aroundAddProduct($subject, $proceed)
{
// do something
// before adding product to cart
// your code goes here
$productId = (int)$this->request->getParam('product', 0);
$qty = (int)$this->request->getParam('qty', 1);
// this will run the core addProduct function
$returnValue = $proceed();
// below code is executed after product is added to cart
if ($returnValue) {
// do something
// after adding product to cart
// your code goes here
}
return $returnValue;
}
}
?>

Related

Cannot declare class Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator because the name is already in use

I'm having a problem with Spatie Media Library. I created my class to use a different filesystem (specifically a Google bucket). Everything works smooth and I can integrate the filesystem correctly, save and view through the custom url. I created my class and gave what "Spatie" describes in its documentation as a namespace namespace Spatie\MediaLibrary\UrlGenerator;
. However, when I run the "artisan config: cache" command I get the error mentioned above.
Here my Custom Class Code extending BaseUrlGenerator:
namespace Spatie\MediaLibrary\UrlGenerator;
use DateTimeInterface;
use Illuminate\Contracts\Config\Repository as Config;
use Illuminate\Filesystem\FilesystemManager;
class GcsUrlGenerator extends BaseUrlGenerator
{
/** #var \Illuminate\Filesystem\FilesystemManager */
protected $filesystemManager;
public function __construct(Config $config, FilesystemManager $filesystemManager)
{
$this->filesystemManager = $filesystemManager;
parent::__construct($config);
}
/**
* Get the url for a media item.
*
* #return string
*/
public function getUrl(): string
{
$url = $this->getPathRelativeToRoot();
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
$url = $this->rawUrlEncodeFilename($url);
$url = $this->versionUrl($url);
return config('medialibrary.gcs.domain').'/'.$url;
}
/**
* Get the temporary url for a media item.
*
* #param \DateTimeInterface $expiration
* #param array $options
*
* #return string
*/
public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string
{
return $this
->filesystemManager
->disk($this->media->disk)
->temporaryUrl($this->getPath(), $expiration, $options);
}
/**
* Get the url for the profile of a media item.
*
* #return string
*/
public function getPath(): string
{
return $this->getPathRelativeToRoot();
}
/**
* Get the url to the directory containing responsive images.
*
* #return string
*/
public function getResponsiveImagesDirectoryUrl(): string
{
$url = $this->pathGenerator->getPathForResponsiveImages($this->media);
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
return config('medialibrary.gcs.domain').'/'.$url;
}
}
Here what I included in the published vendor of medialibrary
'custom_url_generator_class' => \Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator::class,
What I'm missing here?
Thanks for helping me
According to the documentation you should implement the Spatie\MediaLibrary\UrlGenerator interface, not the namespace. Alternatively you can extend Spatie\MediaLibrary\UrlGenerator\BaseUrlGenerator which implements that interface itself. So the namespace of your custom class should still adhere to default naming, meaning it should have namespacing according to the folder structure and classname so it gets autoloaded properly.

Validator event dispatched before Entity validation starts

Question
Is it possible in Symfony 2.8+ / 3.x+ to dispatch event before starting entity validation?
Situation:
Let's say we have 100 entities, they have #LifeCycleCallbacks, they have #postLoad Event that do something, but the result of this is only used for valiation of Entity, in 99% of situations result of #postLoad is unimportant for system. So if we have hundrets or thousands of Entities fetched from DB there will be a lot of machine-cycles lose for unimportant data.
It would be nice to run some kind of event, that will run method, that will populate that data for that specific Entity, just before validations starts.
instead of:
$entity->preValidate();
$validator = $this->get('validator');
$errors = $validator->validate($entity);
we could have:
$validator = $this->get('validator');
$errors = $validator->validate($entity);
And in validate() situation, preValidate() will be dispatched autmaticly as Event (of course with check if Entity does have such method).
CaseStudy:
I have a system that stores pages/subpages as entities. There can be 10 or 10000 pages/subpages
Pages/subpages can have file.
Entities stores only files names (becouse we can't store SplFileInfo - resource serialization restriction)
While Entity->file property is type of string, when I want to make it to be instance of File (so we can do validation of type File) I have something like:
/**
* #postLoad()
*/
public function postLoad()
{
//magicly we get $rootPath
$this->file = new File($rootPath . '/' . $this->file);
}
/**
* #prePersist()
* #preUpdate()
*/
public function preSave()
{
if ($this->file instance of File)
$this->file = $this->file->getFilename();
}
}
Ok, but postLoad() will CHANGE the property, and Doctrine will NOTICE that. So in next
$entityManager->flush()
ALL entities will be flushed - even if preSave() will change it back to be just string as it was before.
So if I have any other entity, let's say TextEntity, and I want to remove it
$entityManager->remove($textEntity);
$entityManager->flush();
All other Entities that are somehow changed (change was noticed by Doctrine), are flushed, no matter if value of file property is the same as in DB (and change was only temporary).
It will be flushed.
So we have hundrets, or thousends of pointless sql updates.
Btw.
1. ->flush($textEntity) will throw Exception, becouse ->remove($textEntity) have already "deleted" that entity.
2. Entity property ->file must be of type File for Assert/File, becouse FileValidator can only accept values of File or absolute-path-to-file.
But I will NOT store absolute-path-to-file, becouse it will be completly different on Dev, Stage, and Production environments.
This is problem that occured when I tried to make file uploading as it was described in Symfony cookbook http://symfony.com/doc/current/controller/upload_file.html.
My solution was to, in postLoad(), create File instance in property that is not Doctrine column, and is anoted to have assertion, etc.
That works, but the problem of useless postLoad()s stays, and i thought about events. That could be elastic, and very elegant solution - instead of controllers getting "fat".
Any one have better solution? Or know how to dispatch event if ->validate() happends?
Hello Voult,
Edit: first method is deprecated in symfony 3 as the thread op mentioned in a comment. Check the second method made for symfony 3.
Symfony 2.3+,Symfony < 3
What I do in this cases, since symfony and most other bundles are using parameters for service class definition, is to extend that service. Check the example below and for more information on extending services check this link
http://symfony.com/doc/current/bundles/override.html
First you need to add a some marker to your entities that require pre-validation. I usually use interfaces for stuff like this something like
namespace Your\Name\Space;
interface PreValidateInterface
{
public function preValidate();
}
After this you extend the validator service
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Validator;
class MyValidator extends Validator //feel free to rename this to your own liking
{
/**
* #inheritdoc
*/
public function validate($value, $groups = null, $traverse = false, $deep = false)
{
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
return parent::validate($value, $groups, $traverse, $deep);
}
}
And final step, you need to add the class value parameter to your 'parameters' config block in config.yml, something like this:
parameters:
validator.class: Your\Name\Space\MyValidator
This is the basic idea. Now you can mix end match this idea with whatever you want to achieve. For instance instead of calling a method on the entity (I usually like to keep business logic outside of my entities), you can look for the interface and if it is there you can launch a pre.validate event with that entity on it, and use a listener to do the job. After that you can keep the result from parent::validate and also launch a post.validate event. You see where i'm going with this. You basically can do whatever you like now inside that validate method.
PS: The example above is the easy method. If you want to go the event route, the service extension will be harder, since you need to inject the dispatcher into it. Check the link I provided at the beginning to see the other way to extend a service and let me know if you need help with this.
For Symfony 3.0 -> 3.1
In this case they managed to make it hard and dirtier to extend
Step 1:
Create your own validator something like this:
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception;
use Symfony\Component\Validator\MetadataInterface;
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class myValidator implements ValidatorInterface
{
/**
* #var ValidatorInterface
*/
protected $validator;
/**
* #param ValidatorInterface $validator
*/
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
/**
* Returns the metadata for the given value.
*
* #param mixed $value Some value
*
* #return MetadataInterface The metadata for the value
*
* #throws Exception\NoSuchMetadataException If no metadata exists for the given value
*/
public function getMetadataFor($value)
{
return $this->validator->getMetadataFor($value);
}
/**
* Returns whether the class is able to return metadata for the given value.
*
* #param mixed $value Some value
*
* #return bool Whether metadata can be returned for that value
*/
public function hasMetadataFor($value)
{
return $this->validator->hasMetadataFor($value);
}
/**
* Validates a value against a constraint or a list of constraints.
*
* If no constraint is passed, the constraint
* {#link \Symfony\Component\Validator\Constraints\Valid} is assumed.
*
* #param mixed $value The value to validate
* #param Constraint|Constraint[] $constraints The constraint(s) to validate
* against
* #param array|null $groups The validation groups to
* validate. If none is given,
* "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validate($value, $constraints = null, $groups = null)
{
//the code you are doing all of this for
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
//End of code
return $this->validator->validate($value, $constraints, $groups);
}
/**
* Validates a property of an object against the constraints specified
* for this property.
*
* #param object $object The object
* #param string $propertyName The name of the validated property
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validateProperty($object, $propertyName, $groups = null)
{
$this->validator->validateProperty($object, $propertyName, $groups);
}
/**
* Validates a value against the constraints specified for an object's
* property.
*
* #param object|string $objectOrClass The object or its class name
* #param string $propertyName The name of the property
* #param mixed $value The value to validate against the
* property's constraints
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
{
$this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups);
}
/**
* Starts a new validation context and returns a validator for that context.
*
* The returned validator collects all violations generated within its
* context. You can access these violations with the
* {#link ContextualValidatorInterface::getViolations()} method.
*
* #return ContextualValidatorInterface The validator for the new context
*/
public function startContext()
{
$this->validator->startContext();
}
/**
* Returns a validator in the given execution context.
*
* The returned validator adds all generated violations to the given
* context.
*
* #param ExecutionContextInterface $context The execution context
*
* #return ContextualValidatorInterface The validator for that context
*/
public function inContext(ExecutionContextInterface $context)
{
$this->validator->inContext($context);
}
}
Step 2:
Extend Symfony\Component\Validator\ValidatorBuilder something like this:
namespace Your\Name\Space;
use Symfony\Component\Validator\ValidatorBuilder;
class myValidatorBuilder extends ValidatorBuilder
{
public function getValidator()
{
$validator = parent::getValidator();
return new MyValidator($validator);
}
}
You need to override Symfony\Component\Validator\Validation. This is the ugly/dirty part, because this class is final so you can't extend it, and has no interface to implement, so you will have to pay attention to in on future versions of symfony in case backward compatibility is broken. It goes something like this:
namespace Your\Name\Space;
final class MyValidation
{
/**
* The Validator API provided by Symfony 2.4 and older.
*
* #deprecated use API_VERSION_2_5_BC instead.
*/
const API_VERSION_2_4 = 1;
/**
* The Validator API provided by Symfony 2.5 and newer.
*/
const API_VERSION_2_5 = 2;
/**
* The Validator API provided by Symfony 2.5 and newer with a backwards
* compatibility layer for 2.4 and older.
*/
const API_VERSION_2_5_BC = 3;
/**
* Creates a new validator.
*
* If you want to configure the validator, use
* {#link createValidatorBuilder()} instead.
*
* #return ValidatorInterface The new validator.
*/
public static function createValidator()
{
return self::createValidatorBuilder()->getValidator();
}
/**
* Creates a configurable builder for validator objects.
*
* #return ValidatorBuilderInterface The new builder.
*/
public static function createValidatorBuilder()
{
return new MyValidatorBuilder();
}
/**
* This class cannot be instantiated.
*/
private function __construct()
{
}
}
And last step overwrite the parameter validator.builder.factory.class in your config.yml:
parameters:
validator.builder.factory.class: Your\Name\Space\MyValidation
This is the least invasive way to do it, that i can find. Is not that clean and it could need some maintaining when you upgrade symfony to future versions.
Hope this helps, and happy coding
Alexandru Cosoi

Codeigniter - My Model Jamie Rumbelow + HMVC callback validation in model

i wonder how to use callback validation using MY model - made by Jamie Rumbelow with Modular extension - wiredesignz
i have create MY_Form_validation and modified the run function like this:
class MY_Form_validation extends CI_Form_validation {
/**
* Stores the CodeIgniter core object.
*
* #access public
*
* #var object
*/
public $CI;
/**
* Constructor
*
* #return void
*/
function __construct($config = array())
{
// Merged super-global $_FILES to $_POST to allow for better file validation inside of Form_validation library
$_POST = (isset($_FILES) && is_array($_FILES) && count($_FILES) > 0) ? array_merge($_POST,$_FILES) : $_POST;
parent::__construct($config);
}
/**
* Performs the actual form validation
*
* #access public
*
* #param string $module Name of the module
* #param string $group Name of the group array containing the rules
*
* #return bool Success or Failure
*/
public function run($module='', $group='')
{
(is_object($module)) AND $this->CI =& $module;
return parent::run($group);
}
it work perfectly if i run form validation with callback in controller, but i wonder how to run in inside a model, it always skip the callback
thank you for your time, and your answer :)

How to use com_users model class in my custom component in joomla

I have created a custom component in Joomla 2.5. In this component I want to fetch all the user 's are available in com_users.For this I want you to know, How can i use com_users model class in to my component. Any one have suggestion's to how to do it.
Depending on where you want use the model you can simply ask Joomla! to load it for you.
In a JController class or sub-class you can call getModel passing in the model name and the components prefix...
e.g.
JModel::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_users/models/');
$model = $this->getModel($name = 'User', $prefix = 'UsersModel');
It may be necessary to add the path of the external model you want to load using JModel::addIncludePath() as show above.
Or if you're sure of the model name and the class prefix you could use JModel's getInstance() to create the desired model object... e.g.
$model = JModel::getInstance('User', 'UsersModel');
Alternatively in a view you could:
$myModel = $this->getModel('myOtherModel');
$this->setModel($myModel);
N.B. In the first line we're passing our desired models name, normally you call getModel without any params to load the default model for your components view controller. In the second line, as we're only passing the model to setModel() it won't make it the default model the view uses.
When we want to use our model objects later on we can specify which we want to use like this:
$item = $this->get('Item');
$otherItem = $this->get('Item', 'myOtherModel' );
The first line uses the view's default model (because we have specified one in the optional parameter). The second line uses the getItem() from myOtherModel.
That's all works because JView (in libraries/joomla/application/view.php) has these methods:
/**
* Method to get the model object
*
* #param string $name The name of the model (optional)
*
* #return mixed JModel object
*
* #since 11.1
*/
public function getModel($name = null)
{
if ($name === null)
{
$name = $this->_defaultModel;
}
return $this->_models[strtolower($name)];
}
/**
* Method to add a model to the view. We support a multiple model single
* view system by which models are referenced by classname. A caveat to the
* classname referencing is that any classname prepended by JModel will be
* referenced by the name without JModel, eg. JModelCategory is just
* Category.
*
* #param JModel &$model The model to add to the view.
* #param boolean $default Is this the default model?
*
* #return object The added model.
*
* #since 11.1
*/
public function setModel(&$model, $default = false)
{
$name = strtolower($model->getName());
$this->_models[$name] = &$model;
if ($default)
{
$this->_defaultModel = $name;
}
return $model;
}
Try something like this
if(!class_exists('UsersModelUser')) require(JPATH_ROOT.DS.'administrator'.DS.'components'.DS.'com_users'.DS.'models'.DS.'user.php');
You can add correct path the model from admin side or front end.
The VM2.x component is using like this way.
Or you need only some details about user you can use.
$user = JFactory::getUser();
Hope this may help you..

Joomla 2.5 variable global in components scope

Im developing custom Joomla 2.5 backend component. I would like to know best practice of making variable global in my components scope. Should I put declarations in components main controller?
class MyController extends JController
{
/**
*
* #var object External database object
*/
public $dbExternal;
function __construct($config = array())
{
$this->dbExternal = $this->_getDbInstance();
JTable::setDbo($this->dbExternal);
parent::__construct($config);
}
/**
* Display task
*
* #return void
*/
function display($cachable = false, $urlparams = false)
{
// set default view if not set
JRequest::setVar('view', JRequest::getCmd('view', 'My'));
// call parent behavior
parent::display($cachable);
// Set the submenu
MyHelper::addSubmenu('messages');
}
/**
* Getting instance of the database
*
* #return instance $dbExternal Return db instance on success
*/
private function _getDbInstance()
{
$params = $this->_getDbParams();
$dbExternal = JDatabase::getInstance($params);
if ($dbExternal->getErrorNum() > 0)
{
JError::raiseWarning(500, 'JDatabase::getInstance: '.
$dbExternal->getErrorMsg());
$this->isError = true;
}
return $dbExternal;
}
/**
* Getting external db parameters
*
* #return array $dbParamsArr Return db params on success
*/
private function _getDbParams()
{
$dbParamsArr = array();
$params = JComponentHelper::getParams('com_my');
$dbParamsArr['host'] = $params->get('db_host');
$dbParamsArr['user'] = $params->get('db_user');
$dbParamsArr['password'] = $params->get('db_password');
$dbParamsArr['database'] = $params->get('db_database');
$dbParamsArr['driver'] = $params->get('db_driver');
return $dbParamsArr;
}
}
So I would like to reach $dbExternal in any other components controller or model. Any ideas?
To access the $dbExternal variable from another component, you'll need to instantiate a MyController object first. The $dbExternal variable does not exist anywhere until you have an instance of MyController.
$myController = new MyController();
$dbExternal = $myController->$dbExternal;

Resources