Sylius - Editable Product Code (SKU) - sylius

I need to implement an SKU code for products, I was just wondering has anybody any thought on the best way to do this. The SKU needs to be editable after creation.
I feel I have a couple of ways:
(Idealy) I would like to use Product.Code, but this is not an editable field after product creation. I would seem I need to override the ProductType#buildForm class/method to not use AddCodeFormSubscriber(). Although I can't seem to figure out how to get the system to use a different form.
Add SKU to the model of Product and figure out how to add it to the ProductType form and again try and figure out how to use a different form.
I am open to suggestions on how to do it the right way.
Would any of the Sylius developers care to elaborate why they decided to make the Code field un-editable?

If you want use product code as editable field in Sylius Beta.1, you can create ProductType extension to current product type and add your custom subscriber which will make code field editable. I did this in my bundle and it works:
create subscriber class wchich will change disabled state to false:
namespace App\Bundle\Form\EventListener;
/* add required namespaces */
/**
* Custom code subscriber
*/
class CustomCodeFormSubscriber implements EventSubscriberInterface
{
private $type;
private $label;
/**
* #param string $type
* #param string $label
*/
public function __construct($type = TextType::class, $label = 'sylius.ui.code')
{
$this->type = $type;
$this->label = $label;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
/**
* #param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$disabled = false;
$form = $event->getForm();
$form->add('code', $this->type, ['label' => $this->label, 'disabled' => $disabled]);
}
}
create form extension and use custom subscriber:
namespace App\Bundle\Form\Extension;
use App\Bundle\Form\EventListener\CustomCodeFormSubscriber;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Sylius\Bundle\ProductBundle\Form\Type\ProductType;
/* use other required namespaces etc */
/**
* Extended Product form type
*/
class ProductTypeExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
/* custom stuff for ur form */
$builder->addEventSubscriber(new CustomCodeFormSubscriber());
}
public function getExtendedType()
{
return ProductType::class;
}
}
register your form extension as a service:
app.form.extension.type.product:
class: App\Bundle\Form\Extension\ProductTypeExtension
tags:
- { name: form.type_extension, priority: -1, extended_type: Sylius\Bundle\ProductBundle\Form\Type\ProductType }

Related

Why is the dynamic serialization group I've created not allowing a mutation for the specified property?

I've implemented a Dynamic Serialization group via Context Builder for admin users (adding admin:write). And have assigned this group to the property I want only updatable by an admin via GraphQL.
My implementation at this point is taken directly from https://api-platform.com/docs/core/graphql/#changing-the-serialization-context-dynamically
But when attempting to mutate this property I am given an error that reads Field "roles" is not defined by type updateUserInput.
This makes some sense to me as the schema does not contain this property since it is not in the typical write group. However, the documentation suggests this should be doable. If this is the case, what am I not doing correctly?
Relevant Code:
Context Builder
<?php
namespace App\Serializer;
use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
* Context Builder: Experimental implementation used for constructing what resources are returned.
*/
final class AdminGroupsContextBuilder implements SerializerContextBuilderInterface {
private $decorated;
private $authorizationChecker;
/**
*
*/
public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker) {
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
/**
*
*/
public function create(?string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array {
$context = $this->decorated->create($resourceClass, $operationName, $resolverContext, $normalization);
$resourceClass = $context['resource_class'] ?? NULL;
if (isset($context['groups']) && $this->authorizationChecker->isGranted('ROLE_ADMIN') && FALSE === $normalization) {
$context['groups'][] = 'admin:input';
}
return $context;
}
}
User Entity Class property definition
/**
* #ORM\Column(type="json")
* #Groups({"read", "admin:write"})
*/
private $roles = [];
Services Definition
App\Serializer\AdminGroupsContextBuilder:
decorates: 'api_platform.graphql.serializer.context_builder'
arguments: [ '#App\Serializer\AdminGroupsContextBuilder.inner' ]
autoconfigure: false

Symfony 3 get current user inside entity

I was wondering if there is a way that i can initialize the property owner with an entity User of FOSUserBundle so that it contains the user who created the Post
I want to do this inside the constructor as shown below.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post
{
/* here are defined some attributs */
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #ORM\JoinColumn(name="owner", referencedColumnName="id")
*/
private $owner;
public function __construct()
{
$this->owner = /* get current user */ ;
}
}
Is there a way to do this by replacing the comment in the constructor with something ?
Thank you for your answers
No, there isn't. [*]
There are at least two ways to deal with this:
Create your Post entities through a factory service which populates the
owner property:
namespace My\Bundle\EntityFactory;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostFactory
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function createPost()
{
$user = $this->tokenStorage()->getToken()->getUser();
$post = new Post($user);
}
}
(for this example, you will have to modify your Post constructor to
accept the owner as a parameter)
In services.yml:
services:
post_factory:
class: My\Bundle\EntityFactory\PostFactory
arguments: [#security.token_storage]
To create an entity from your controller:
$post = $this->container->get('post_factory')->createPost();
If you can tolerate that the owner will only be set once you persist the
entity, you can use a doctrine event listener:
namespace My\Bundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostOwnerAssignmentListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
if ($entity instanceof Post && !$entity->getOwner()) {
$entity->setOwner($this->tokenStorage->getToken()->getUser());
}
}
}
In services.yml:
services:
post_owner_assignment_listener:
class: My\Bundle\EventListener\PostOwnerAssignmentListener
arguments: [#security.token_storage]
tags:
- { name: doctrine.event_listener, event: prePersit }
The advantage here is that the owner gets assigned no matter how and where
the Post is created.
[*]: Well, technically with the default app.php you could access the
kernel by declaring global $kernel; in your constructor and go from there,
however this is very strongly discouraged and may break in strange and subtle
ways.
I think you are way over-complicating this issue. When you create a new Post in your controller, either in the controller or in the repository do something like this:
use AppBundle\Entity\Post; //at top of controller
$em = $this->getDoctrine()->getManager();
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$post = new Post();
$em->persist( $post );
$post->setOwner( $user );
// set other fields in your post entity
$em->flush();
For Symfony 4+ with Autowiring and Entity event listener:
In /EventListener/PostPrePersistListener.php:
namespace App\EventListener;
use App\Entity\Post;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class PostPrePersistListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(Post $post, LifecycleEventArgs $event)
{
$post->setOwner($this->tokenStorage->getToken()->getUser());
}
}
In services.yaml:
services:
App\EventListener\PostPrePersistListener:
autowire: true
tags:
- { name: doctrine.orm.entity_listener, entity: 'App\Entity\Post', event: prePersist }
Modifying services.yaml is required as Symfony cannot know that this custom service is tagged to hook on doctrine.event_listener
This works at Entity-level as asked, to ensure Controller do not handle the owner value.

How do I change default pagination attributes in Yii?

In my Yii project, I want the default pageSize for pagination to be fetched automatically, so I don't have to specify it in all the widgets that use pagination. But I can't seem to find a way to globally change the pagination class, without editing Yii source files. Is this possible?
Please create file on /components/WidgetFactory.php with below code.
<?php
/**
* Custom WidgetFactory class
* Provides two new events:
* - onBeforeCreateWidget
* - onAfterCreateWidget
*
* Allows for advanced global widget alteration, going a step further than CWidgetFactory's
* typical process which allows you to define default values for widgets.
*
*/
class WidgetFactory extends CWidgetFactory
{
/**
* Raised right BEFORE a widget is created.
* #param CEvent $event the event parameter
*/
public function onBeforeCreateWidget(CEvent $event)
{
$this->raiseEvent('onBeforeCreateWidget',$event);
}
/**
* Raised right AFTER a widget is created.
* #param CEvent $event the event parameter
*/
public function onAfterCreateWidget(CEvent $event)
{
$this->raiseEvent('onAfterCreateWidget',$event);
}
/**
* Creates a new widget based on the given class name and initial properties.
* #param CBaseController $owner the owner of the new widget
* #param string $className the class name of the widget. This can also be a path alias (e.g. system.web.widgets.COutputCache)
* #param array $properties the initial property values (name=>value) of the widget.
* #return CWidget the newly created widget whose properties have been initialized with the given values.
*/
public function createWidget($owner,$className,$properties=array())
{
if (! ($this->hasEventHandler('onBeforeCreateWidget') || $this->hasEventHandler('onAfterCreateWidget')))
return parent::createWidget($owner, $className, $properties);
$event=new WidgetEvent($this, $owner, $className, $properties);
if ($this->hasEventHandler('onBeforeCreateWidget'))
$this->raiseEvent('onBeforeCreateWidget', $event);
$event->widget=parent::createWidget($owner, $className, $properties);
if ($this->hasEventHandler('onAfterCreateWidget'))
$this->raiseEvent('onAfterCreateWidget', $event);
return $event->widget;
}
}
class WidgetEvent extends CEvent
{
/**
* #var CBaseController Owner of the new widget
*/
public $owner;
/**
* #var string Widget class name
*/
public $className;
/**
* #var CWidget The newly created widget
*/
public $widget;
/**
* Constructor.
* #param WidgetFactory $sender The WidgetFactory instance
* #param CBaseController $owner The owner of the new widget
* #param string $className The class name of the widget. This can also be a path alias.
* #param array $params The initial property values (name=>value) of the widget.
*/
public function __construct(WidgetFactory $sender, CBaseController $owner, $className, array $params=array())
{
parent::__construct($sender, $params);
$this->owner=$owner;
$this->className=$className;
}
}
And correct config/main.php like below.
return array(
// ...
'components'=>array(
// ...
'widgetFactory'=>array(
'class'=>'WidgetFactory',
'onAfterCreateWidget'=>function(WidgetEvent $event){
static $defaultPageSize=50; // YOUR_DEFAULT_PAGESIZE_HERE
$widget=$event->widget;
if ($widget instanceof CBaseListView) {
/** #var CBaseListView $widget */
if ($widget->dataProvider!==null && $widget->dataProvider->pagination!==false)
$widget->dataProvider->pagination->pageSize=$defaultPageSize;
}
},
),
// ...
),
);
Please notice default pageSize above on config code . I think it will solve your problem.

TYPO3 - extending an extbase extension with new fields and using these in fluid templates

I'm trying to extend powermail (version 2) with the possibility to add a note for each input field. So far I have created a new extension using extension builder and with a few modifications to ext_tables.php the field show up in the backend. The new field is called 'note' and I thought I could just do something like {field.note} in the fluid template input.html, but that does not work. My model includes the setter and getter:
class Tx_Formnotes_Domain_Model_Powermailnotes extends Tx_Extbase_DomainObject_AbstractEntity {
/**
* note
*
* #var string
*/
protected $note;
/**
* Returns the note
*
* #return string $note
*/
public function getNote() {
return $this->note;
}
/**
* Sets the note
*
* #param string $note
* #return void
*/
public function setNote($note) {
$this->note = $note;
}
}
What else is needed?
Info: I'm using TYPO3 4.7
You could map the powermail model like
config.tx_extbase.persistence.classes {
Tx_Formnotes_Domain_Model_Powermailnotes {
mapping {
tableName = powermailTableName
columns {
exampleMedia.mapOnProperty = media
}
}
}
}
after that you should extend your TCA with these properties. At least you can write setter and getter for each property and use them in your fluid template.

Yii Framework - from url to route

I searched, but couldnt find something.
So, I have route rules:
...
'/reg' => '/user/user/registration',
...
in
Yii::app()->request
I couldn find any route information.
So, how can I get in module init function and having only url, route lile
/reg -> user/user/registration
UPD
The route is only available from the running controller. By the time when a module is initialized the controller is not yet available, thus you can't find out the route there. (You can follow CWebApplication::processRequest to see what happens when a request is resolved up to the point where the controller is run.)
It depends on what you try to achieve, but you could override WebModule::beforeControllerAction to do something before the module controller is run.
Today (next day after my question), I could solve this.
I will try to explain:
As Michael wrote, we cant know in module in which controller we are.
But I net get just reversed route, so, its quite esay.
Yii::app()->getUrlManager()->parseUrl('/reg');
This will return my reversed route
user/user/registration
parseUrl
Solution for Yii 1.1.15 workes for me.
class HttpRequest extends CHttpRequest {
protected $_requestUri;
protected $_pathInfo;
public function setUri($uri){
$this->_requestUri = $uri;
}
public function setPathInfo($route){
$this->_pathInfo = $route;
}
public function getPathInfo(){
/* copy from parent */
}
public function getRequestUri(){
/* copy from parent */
}
}
The usage:
$uri_path = 'my/project-alias/wall';
/** #var HttpRequest $request */
$request = clone Yii::app()->getRequest();
$request->setUri($uri_path);
$request->setPathInfo(null);
$route = Yii::app()->getUrlManager()->parseUrl($request);
//$route equals 'project/profile/wall' etc here (like in route rules);
I'm using a slightly different sub-class of CHttpRequest:
class CustomHttpRequest extends \CHttpRequest
{
/**
* #var string
*/
var $pathInfo;
/**
* #var string
*/
private $method;
public function __construct($pathInfo, $method)
{
$this->pathInfo = $pathInfo;
$this->method = $method;
}
public function getPathInfo()
{
return $this->pathInfo; // Return our path info rather than the default
}
public function getRequestType()
{
return $this->method;
}
}
Then to call it (to create a controller, which is what I want):
$request = new CustomHttpRequest($uri, $method); // e.g. 'my/project-alias/wall' and 'GET'
$route = \Yii::app()->getUrlManager()->parseUrl($request);
list($jcontroller, $actionName) = \Yii::app()->createController($route);