I'm trying to create an image fixture for SonataMediaBundle. The media is saved in the database and shown in the admin interface, but the image is missing. There is no difference between the records created by fixture and admin interface.
Here is my Fixture in AppBundle\DataFixtures\ORM\LoadMediaBundleMediaData
<?php
namespace AppBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\File\File;
use Application\Sonata\ClassificationBundle\Entity\Context;
use Application\Sonata\ClassificationBundle\Entity\Category;
class LoadMediaBundleMediaData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
private $container;
/**
* {#inheritDoc}
*/
public function load(ObjectManager $manager)
{
$context = new Context();
$context->setId('default');
$context->setName('default');
$context->setEnabled(true);
$manager->persist($context);
$manager->flush();
$category = new Category();
$category->setName('Default');
$category->setContext($context);
$category->setEnabled(true);
$manager->persist($category);
$manager->flush();
$media1File = new File(__DIR__ . '/../Assets/Images/image.jpg');
$mediaManager = $this->container->get('sonata.media.manager.media');
$media1 = $mediaManager->create();
$media1->setEnabled(true);
$media1->setBinaryContent($media1File);
$media1->setName('brunnkogel.jpg');
$media1->setContext('default');
$media1->setProviderName('sonata.media.provider.image');
$media1->setProviderStatus(1);
$mediaManager->save($media1);
$manager->persist($media1);
$manager->flush();
}
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* {#inheritDoc}
*/
public function getOrder()
{
return 40;
}
}
If I create a media in the admin interface it works and the image is saved at web/uploads/media/default/0001/01/c4412442f8f146db6acaec10751fb54e4c80e8c5.jpeg. Also the thumbnails are in this directory. If I create the media by fixtures, the files in web/uploads/media are missing.
I'm using Symfony 2.8 and SonataMediaBundle 3.0.
How can I solve this issue?
Related
How can I test Services with PHPUnit using symfony? So far, I installed and included test-pack, DAMA Doctrine Bundle, and created Test Database.
Inside .env.test I added Database connection
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
# .env.test.local
DATABASE_URL="mysql://root:root#db:3306/testdb?serverVersion=mariadb-10.4.11&charset=utf8mb4"
I included inside phpunit.xml.dist the DAMA Doctrine bundle
<extensions>
<extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
</extensions>
Now, what I want to test is my Services (for instance CartService, ProductService etc.)
use App\Entity\Cart;
use App\Entity\CartItem;
use App\Entity\Product;
use App\Entity\User;
use App\Repository\CartItemRepository;
use App\Repository\CartRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Security;
class CartService
{
private CartRepository $cartRepository;
private ManagerRegistry $managerRegistry;
private CartItemRepository $cartItemRepository;
private Security $security;
public function __construct(Security $security, CartItemRepository $cartItemRepository, CartRepository $cartRepository, ManagerRegistry $managerRegistry)
{
$this->cartItemRepository = $cartItemRepository;
$this->cartRepository = $cartRepository;
$this->managerRegistry = $managerRegistry;
$this->security = $security;
}
/**
* Get Cart by ID
*
* #return Cart|null
*/
public function getCartByUserId(): ?Cart
{
$user = $this->security->getUser();
return $this->cartRepository->findOneBy(['customer' => $user]);
}
/**
* Show Cart and Total Price
*
* #return Cart|null
*/
public function showCart(): ?Cart
{
$cart = $this->getCartByUserId();
$this->calculateTotalPrice();
return $cart;
}
When I run phpunit test on CartServiceTest, I get this error:
1) App\Tests\CartServiceTest::testShowCart
Error: Typed property App\Tests\CartServiceTest::$cartService must not be accessed before initialization
/var/www/html/Tests/CartServiceTest.php:29
CartServiceTest look like this
<?php
namespace App\Tests;
use App\Entity\Product;
use App\Service\CartService;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CartServiceTest extends KernelTestCase
{
/**
* #var EntityManager
*/
private EntityManager $entityManager;
private CartService $cartService;
public function setUp(): void
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager();
}
public function testShowCart()
{
$user = 11;
$cart = $this->cartService->getCartByUserId();
dump($cart);
}
protected function tearDown(): void
{
$this->entityManager->close();
}
}
Error: Typed property App\Tests\CartServiceTest::$cartService must not be accessed before initialization
Means that you have to already Use the cartService in your application. For exemple if you already inject this service has a dependency injection in one of your controller it's okay.
But you can do better. Just create a service config for your tests "services_test.yaml" and make your service public
Something like:
#servies_test.yaml
services:
App\Service\CartService:
public: true
I have an abstract class called AbstractMediaService and a some specific implementations of this abstract class:
abstract class AbstractMediaService
{
private $em;
private $media;
public function __construct(EntityManagerInterface $em, Media $media)
{
$this->em = $em;
$this->media = $media;
}
public function dosomethingInCommon();
abstract public function dosomethingSpecific();
}
class PhotoMediaService extends AbstractMediaService
{
public function dosomethingSpecific()
{
echo 'i am a photo service';
}
}
class VideoMediaService extends AbstractMedia
{
public function dosomethingSpecific()
{
echo 'i am a video service';
}
}
These objects require a Media entity to work with
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
class Media
{}
Controller
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods={"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media)
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$media = new PhotoMedia($media);
// return response
}
Problem is that I need some dependencies like the Security service or the EntityManager.
I would like to know how autowire AbstractMediaService service.
This is wrong. You cannot autowire Media to be injected into a service, because entities are not services.
public function __construct(EntityManagerInterface $em, Media $media)
If VideoMediaService and PhotoMediaService (I renamed them for clarity, since sharing the name with your entity made it look like it were related) need an instance of Media to perform some work, just make that a parameter for the corresponding methods.
public function dosomethingInCommon(Media $media);
abstract public function dosomethingSpecific(Media $media);
Or alternatively, simply have a setMedia(Media $media) method on that class for that:
public function setMedia(Media $media) {
$this->media = $media;
}
Frankly, this latter approach does not seem like a great idea. You would need to make the methods that work on $media aware of the possibility of setMedia() not having been called yet, or subsequent calls to setMedia() would change how the service behaved. Just making it a parameter of the appropriate method is much cleaner, clearer and safer.
Injecting those services is done like any other service. That they extend an abstract class is irrelevant.
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods= {"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media, PhotoMediaService $photoMediaService): Response
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$photoMediaService->doSomethingSpecific($media)
return new Response('all done');
}
I want to add a new UserTeam(which get a team, a user and a role) each time a User create a Team. I created an event subscriber TeamFirstUserAdminSubscriber.php but it doesn't work and I have no error message.
here is my database model:
and here is the file TeamFirstUserAdminSubscriber.php
<?php
namespace App\Events;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Team;
use App\Entity\UserTeam;
use App\Repository\RoleUserTeamRepository;
use App\Repository\TeamRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class TeamFirstUserAdminSubscriber implements EventSubscriberInterface
{
private $security;
private $repository;
private $repositoryT;
private $manager;
public function __construct(Security $security, RoleUserTeamRepository $repository, TeamRepository $repositoryT, EntityManagerInterface $manager)
{
$this->security = $security;
$this->repository = $repository;
$this->repositoryT = $repositoryT;
$this->manager = $manager;
}
public static function getSubscribedEvents()
{
return[
KernelEvents::VIEW => ['setInstanceUserTeam', EventPriorities::POST_WRITE],
];
}
public function setInstanceUserTeam(ViewEvent $event)
{
$team = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if($team instanceof Team && $method === 'POST')
{
//get the user connected
$user = $this->security->getUser();
//get the last team created
$lastTeam = $this->repositoryT->findLastTeamCreated();
//get the admin role (NOT the symfony one)
$admin = $this->repository->findOneByRoleAdmin('Admin');
//should create a new UserTeam instance with the User, the Team and the RoleUserTeam wanted
$userTeam = new UserTeam();
$userTeam->setUser($user);
$userTeam->setTeam($lastTeam);
$userTeam->setRoleUserTeam($admin);
$manager = $this->manager;
$manager->persist($userTeam);
$manager->flush();
}
}
The new UserTeam is not created in the databse when I try it out with postman, but the Team is well created.
I think I am missing something but I don't know what.
Could anyone help me ?
I can't see your full code(controller, forms etc), but you can achieve this without an event listener. As you already know, what team to be assigned for a user, why don't you assign that on the entity when creating the user object. For example if you using form in the controller
public function addAdminUser(Request $request, RoleUserTeamRepository $repository, TeamRepository $repositoryT, EntityManagerInterface $manager)
{
$user = new User();
$form = $this->createForm(UserForm::class, $user);
$form->handleRequest($form);
if ($form->isSubmitted && $form->isValid()) {
//get the last team created
$lastTeam = $this->repositoryT->findLastTeamCreated();
//get the admin role (NOT the symfony one)
$admin = $this->repository->findOneByRoleAdmin('Admin');
//should create a new UserTeam instance with the User, the Team and the
RoleUserTeam wanted
$userTeam = new UserTeam();
$userTeam->setUser($user);
$userTeam->setTeam($lastTeam);
$userTeam->setRoleUserTeam($admin);
$user->setUserTeam($userTeam);
//persist the user
$manager->persist($user);
$manager->flush();
}
}
If you want to use events I would suggest use Doctrine events. You can use prePersist event on user entity to achieve same result. Doctrine Events
I solved my problem, I needed a cascade persist in my entities. Here, if it can hepl some people :
I had this in the Team entity
public function __construct()
{
$this->id = Team::class;
$this->userTeams = new ArrayCollection();
$this->categories = new ArrayCollection();
}
public function userTeam()
{
$newUserTeam = new UserTeam();
$newUserTeam->setTeam($this);
$this->userTeams->add($newUserTeam);
}
UserTeam entity, I had to add cascade={"persist"} in the relation ManyToOne:
class UserTeam
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Team::class, inversedBy="userTeams", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
* #Groups({"users_read", "userTeam_read"})
*/
private $team;
And the TeamByUserSubscriber that I modified :
<?php
namespace App\Events;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Team;
use App\Entity\UserTeam;
use App\Repository\RoleUserTeamRepository;
use App\Repository\TeamRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class TeamByUserSubscriber implements EventSubscriberInterface
{
private $security;
private $repository;
private $entityManager;
public function __construct(Security $security, RoleUserTeamRepository $repository, EntityManagerInterface $entityManager)
{
$this->security = $security;
$this->repository = $repository;
$this->entityManager = $entityManager;
}
public static function getSubscribedEvents()
{
return[
KernelEvents::VIEW => ['setUserForTeam', EventPriorities::PRE_VALIDATE],
];
}
public function setUserForTeam(ViewEvent $event)
{
$team = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if($team instanceof Team && $method === 'POST')
{
//get the connected User
$user = $this->security->getUser();
//put the current User to the team we are creating (as the creator of the team)
$team->setUser($user);
//get the Admin role from the RoleUserTeam entity
$admin = $this->repository->findOneByRoleAdmin('Admin');
//create a new instance of UserTeam with the connected User, the Team we are creating and the role
$userTeam = new UserTeam();
$userTeam->setUser($user);
$userTeam->setTeam($team);
$userTeam->setRoleUserTeam($admin);
$manager = $this->entityManager;
$manager->persist($userTeam);
$manager->flush();
}
}
I need to run the controller method every 2 hours. I read somewhere that you need to create a command and run this command by using CRON. It is correct?
MY COMMAND:
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Annotation\Route;
class RunCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'app:run';
protected function configure()
{
// ...
}
protected function execute(InputInterface $input, OutputInterface $output)
{
echo 'BEGIN';
$controller = new \AppBundle\Controller\DefaultController();
$controller->storeAction();
echo 'END';
}
}
MY CONTROLLER:
/**
* #Route("/to-db", name="to-db")
*/
public function storeAction()
{
$entityManager = $this->getDoctrine()->getManager();
$data = new Skuska();
$data->setName('Keyboard');
$entityManager->persist($data);
$entityManager->flush();
// die();
}
My error: In ControllerTrait.php line 424: Call to a member function has() on null
Is my code correct? How do I run a method using cron?
I don't want to use another bundle. I want to program it myself
As mentioned in the comments, you should move the logic out of the controller and into a service, and use that service both in the command and in the controller.
With the default service autoloading configuration, you don't even have to care about your service declarations. Your command will automatically be a service, and you can inject other services into it.
https://symfony.com/doc/current/console/commands_as_services.html
For controllers, you don't even need to use a specific constructor.
https://symfony.com/doc/current/controller.html#fetching-services
<?php
// AppBundle/Service/StoreService.php
use AppBundle\Entity\Skuska;
use Doctrine\ORM\EntityManager;
class StoreService
{
/** #var EntityManager */
private $entityManager;
/**
* StoreService constructor.
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function store()
{
$data = new Skuska();
$data->setName('Keyboard');
$this->entityManager->persist($data);
$this->entityManager->flush();
}
}
<?php
// AppBundle/Controller/StoreController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Service\StoreService;
class StoreController extends Controller
{
/**
* #Route("/to-db", name="to-db")
* #param StoreService $storeService
* #return Response
*/
// Hinting to you service like this should be enough for autoloading.
// No need for a specific constructor here.
public function storeAction(StoreService $storeService)
{
$storeService->store();
return new Response(
// Return something in you response.
);
}
}
<?php
// AppBundle/Command/RunCommand.php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use AppBundle\Service\StoreService;
class RunCommand extends Command
{
protected static $defaultName = 'app:run';
/** #var StoreService */
protected $storeService;
/**
* RunCommand constructor.
* #param StoreService $storeService
*/
public function __construct(StoreService $storeService)
{
$this->storeService = $storeService;
parent::__construct();
}
protected function configure()
{
// ...
}
protected function execute(InputInterface $input, OutputInterface $output)
{
echo 'BEGIN';
$this->storeService->store();
echo 'END';
}
}
I'm trying to pre-populate a database with some User objects, but when I call $user->setPassword('some-password'); and then save the user object, the string 'some-password' is stored directly in the database, instead of the hashed+salted password.
My DataFixture class:
// Acme/SecurityBundle/DataFixtures/ORM/LoadUserData.php
<?php
namespace Acme\SecurityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\SecurityBundle\Entity\User;
class LoadUserData implements FixtureInterface
{
public function load(ObjectManager $manager)
{
$userAdmin = new User();
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPassword('test');
$manager->persist($userAdmin);
$manager->flush();
}
}
And the relevant database output:
id username email salt password
1 System system#example.com 3f92m2tqa2kg8cookg84s4sow80880g test
Since you are using FOSUserBundle, you can use UserManager to do this. I would use this code (assuming you have $this->container set):
public function load(ObjectManager $manager)
{
$userManager = $this->container->get('fos_user.user_manager');
$userAdmin = $userManager->createUser();
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPlainPassword('test');
$userAdmin->setEnabled(true);
$userManager->updateUser($userAdmin, true);
}
Call setPlainPassword instead.
<?php
namespace Acme\SecurityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\SecurityBundle\Entity\User;
class LoadUserData implements FixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$userAdmin = new User();
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPlainPassword('test');
$userAdmin->setRoles(array('ROLE_SUPER_ADMIN'));
$manager->persist($userAdmin);
$manager->flush();
}
}
Four lines of code and you are done. It will handle everything for you:
$userManager = $this->container->get('fos_user.user_manager');
$user->setPlainPassword($password);
$userManager->updatePassword($user);
This worked for me
public function load(ObjectManager $manager){
$userAdmin = new User();
$userAdmin->setUsername('admin');
$userAdmin->setPlainPassword('admin');
$userAdmin->setEmail('admin#gmail.com');
$userAdmin->setEnabled(true);
$manager->persist($userAdmin);
$manager->flush();
}
Note the difference when setting the password. Querying the database you find
id username username_canonical email email_canonical enabled salt password
2 admin admin admin#gmail.com admin#gmail.com 1 4gm0bx6jzocgksw0wws8kck04kg40o8 m2ZyJM2+oBIzt/NZdnOX4nFvjV/SWTU1qJqe6dWZ0UwLF5gB8N...
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPlainPassword('test');
$userAdmin->setEnabled(true);
setPlainPassword works for me.
/**
* 添加用户
* #param $param
* #return int
*/
public function doAdd($param)
{
$entity = new User();
$em = $this->getEntityManager();
$entity->setUsername($param['username'])
->setPlainPassword($param['password'])
->setEmail($param['email'])
->setEnabled(true)
->setRealName($param['realName']);
$em->persist($entity);
$em->flush();
return $entity->getId();
}
Above worked for me, so I got some conclusion:
1. must use the setPlainPassword
2. must setEnabled(true)
Here a sample class to create an admin user via ORM Fixtures:
<?php
namespace Acme\SecurityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Acme\SecurityBundle\Entity\User;
class LoadFOSAdminUser extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$userManager = $this->container->get('fos_user.user_manager');
$userAdmin = $userManager->createUser();
$userAdmin->setUsername('admin');
$userAdmin->setEmail('admin#example.com');
$userAdmin->setPlainPassword('admin');
$userAdmin->setEnabled(true);
$userAdmin->setRoles(array('ROLE_ADMIN'));
$userManager->updateUser($userAdmin, true);
}
public function getOrder()
{
return 1;
}
}