Задать вопрос
Golang/PHP(Symfony)/Typescript (Angular) developer.
Контакты
Местоположение
Россия, Москва и Московская обл., Москва

Достижения

Все достижения (78)

Наибольший вклад в теги

Все теги (247)

Лучшие ответы пользователя

Все ответы (1114)
  • Как сделать рандом с процентами?

    Maksclub
    @Maksclub Куратор тега PHP
    Можно для каждого значения сделать вес, можете сделать так, чтобы сумма все х весов была 100,
    но не обязательно — это могут быть произвольные значения. Главное, что доля веса от суммы всех весов — будет вероятностью данного элемента.

    Такой способ делает ваши значения управляемыми буквально одним параметром веса.
    И не нужно шатать код.

    Обновление от 13.06.2020:
    Так вышло, что мой тимлид математик и нашел интересное и эффективное решение этой задачи
    <?php
    
    $values = [
        ['value' => 'One', 'weight' => 20],
        ['value' => 'Two', 'weight' => 30],
        ['value' => 'Three', 'weight' => 50]
    ];
    
    function randByWeight(array $arr) {
        $max = 0;
        $result = [];
        foreach($arr as $value) {
            $rand = pow((mt_rand() / (mt_getrandmax() + 1)), 1/$value['weight']);
            if ($rand > $max) {
                $max = $rand;
                $result = $value;
            }
        } 
    
        return $result;
    }
    
    // Например: 'Three' выпадет в 50% случаев, тк его вес -- половина от суммы всех весов
    // Например: 'Two' выпадет в 30% случаев, тк его вес -- 30% от суммы всех весов
    var_dump(randByWeight($values));

    sandbox.onlinephpfunctions.com/code/39de2d54a298de...

    Чем хорошо это решение — его можно использовать прямо в SQL запросе!
    SELECT * FROM table ORDER BY POWER(random(), 1/weight) DESC LIMIT 1


    Классическое решение:
    <?php
    
    $values = [
        ['value' => 'One', 'weight' => 20],
        ['value' => 'Two', 'weight' => 30],
        ['value' => 'Three', 'weight' => 50]
    ];
    
    function randByWeight(array $items) {
        $sum = array_reduce($items, function(int $acc, array $item): int {
            return $acc += $item['weight'];
        }, 0);
        
        $rand = (mt_rand() / (mt_getrandmax() + 1)) * $sum;
    
        foreach($items as $item) {
            if($rand < $item['weight']) {
                return $item;
            }
            
            $rand -= $item['weight'];
        }
    }
    
    // Например: 'Three' выпадет в 50% случаев, тк его вес -- половина от суммы всех весов
    // Например: 'Two' выпадет в 30% случаев, тк его вес -- 30% от суммы всех весов
    var_dump(randByWeight($values));

    sandbox.onlinephpfunctions.com/code/aee9dca651213e...
    Ответ написан
    3 комментария
  • Class Table Inheritance - Querying Inherited Classes по полям Inherited сущности?


    Решение не заработает, тк поиск будет по полю родительской сущности
    $qb->expr()->andX(
        $qb->expr()->andX(
            $qb->expr()->isInstanceOf('sub1',  'TargetClass'),
            $qb->expr()->eq('sub1.sub1Field',  $sub1FieldValue)
        )
    );



    UPD2 (рекомендация): Не решать такие задачи через ORM, а делать просто SQL.

    UPD1: решение без instanceOf().
    Можно найти решение по красивее, но вот на коленке
    • Код не идеальный, но работает, как вариант
    • Можно выражения (равенство, <> и прочее тоже вынести на уровень передачи параметра)
    • Или заменить на вида Expr\Andx(), что даст много гибкости
    • Ну и проверок добавить


    Сущности:

    /**
     * @ORM\Entity
     * @ORM\InheritanceType("JOINED")
     * @ORM\DiscriminatorColumn(name="discr", type="string")
     * @ORM\DiscriminatorMap({"cat" = "Cat", "dog" = "Dog"})
     *
     * @ORM\Entity(repositoryClass="AnimalRepository")
     */
    class Animal
    {
    	/**
    	 * @ORM\Id
    	 * @ORM\Column(type="integer", nullable=true)
    	 * @ORM\GeneratedValue(strategy="AUTO")
    	 */
    	public ?string $id = null;
    }
    
    
    /** @ORM\Entity */
    class Cat extends Animal
    {
    	/**
    	 * @ORM\Column(type="string")
    	 */
    	public $meow;
    }
    
    
    /** @ORM\Entity */
    class Dog extends Animal
    {
    	/**
    	 * @ORM\Column(type="string")
    	 */
    	public $woof;
    }



    Repository:

    class AnimalRepository extends EntityRepository
    {
    	/**
    	 * @param array $filters
    	 *
    	 * @return \Generator<Animal::class>|Animal[]
    	 */
    	public function filter(array $filters): \Generator
    	{
    		foreach ($filters as $filter) {
    			[$entityClass, $conditions] = $filter;
    
    			if (!is_a($entityClass, Animal::class, true)) {
    				throw new \LogicException('Класс должен быть ребенком ' . Animal::class);
    			}
    
    			yield  from $this->getSubEntities($entityClass, [$conditions]);
    		}
    	}
    
    	private function getSubEntities(string $subEntityClassName, array $conditions): \Generator
    	{
    		$qb = $this->getEntityManager()->createQueryBuilder();
    		$qb->select('e')->from($subEntityClassName, 'e');
    
    		foreach ($conditions as $condition) {
    			[$param, $paramValue] = $condition;
    
    			$qb
    				->andWhere("e.$param = :$param")
    				->setParameter($param, $paramValue);
    		}
    
    		foreach ($qb->getQuery()->getResult() as $entity) {
    			yield $entity;
    		}
    	}
    }


    Тест (проходит)
    class AnimalRepositoryCest
    {
    	/** @var EntityManagerInterface */
    	private $em;
    
    	public function _before(FunctionalTester $I): void
    	{
    		$this->em = $I->grabService('doctrine.orm.entity_manager');
    	}
    
    	public function testSuccessFilter(): void
    	{
    		$cat1 = new Cat();
    		$cat1->meow = 'meow1';
    
    		$cat2 = new Cat();
    		$cat2->meow = 'meow2';
    
    		$dog1 = new Dog();
    		$dog1->woof = 'woof1';
    
    		$dog2 = new Dog();
    		$dog2->woof = 'woof2';
    
    		$this->em->persist($cat1);
    		$this->em->persist($cat2);
    		$this->em->persist($dog1);
    		$this->em->persist($dog2);
    		$this->em->flush();
    
    		/** @var AnimalRepository $animalRepository */
    		$animalRepository = $this->em->getRepository(Animal::class);
    		assertInstanceOf(AnimalRepository::class, $animalRepository);
    
    		// этот код можно в репозитории сокрыть
    		$resultIter = $animalRepository->filter([
    			Cat::class, [['meow' => 'meow2']],  // тут точка расширения гибкости добавления условий, чтобы все не только eq() было
    			Dog::class, [['woof' => 'woof1']],
    		]);
    		$result = iterator_to_array($resultIter);
    		assertCount(2, $result);
    
    		assertInstanceOf(Cat::class, $result[0]);
    		assertSame('meow2', $result[0]->meow);
    
    		assertInstanceOf(Dog::class, $result[1]);
    		assertSame('woof1', $result[1]->woof);
    	}
    }

    Ответ написан
  • Как замокать приватное свойство в классе phpunit?

    Имитация вашего объекта:
    class Relay
    {
        private $privateProperty;
    
        public function __construct()
        {
            $this->privateProperty = new \stdClass();
        }
    
        // метод, который работает с приватным свойством
        public function call()
        {
            return $this->privateProperty;
        }
    }


    Способ 1: Нативный PhpUnit + Reflection API
    use PHPUnit\Framework\TestCase;
    
    class RelayTest extends TestCase
    {
        public function testCall(): void
        {
            $reflectionClass = new \ReflectionClass(Relay::class);
            $reflectionProperty = $reflectionClass->getProperty('privateProperty');
            $reflectionProperty->setAccessible(true);
    
            // создаем наш объект БЕЗ конструктора
            $relay = $reflectionClass->newInstanceWithoutConstructor();
    
            // Меняем свойство и вызываем метод, работающий с этим приватным полем
            $reflectionProperty->setValue($relay, 1111);
            self::assertEquals(1111, $relay->call());
    
            // Меняем свойство и вызываем метод, работающий с этим приватным полем
            $reflectionProperty->setValue($relay, 'aaaa');
            self::assertEquals('aaaa', $relay->call());
        }
    }


    Способ 2: Через Codeception Stub
    class RelayTest extends TestCase
        public function testCall(): void
        {
            /** @var Example $stub */
            $stub = Stub::make(Relay::class, [
                'privateProperty' => 1111,
            ]);
            self::assertEquals(1111, $stub->call());
    
            $stub = Stub::make(Relay::class, [
                'privateProperty' => 'aaaa',
            ]);
            self::assertEquals('aaaa', $stub->call());
        }
    }

    Отступления и полезные советы:
    • Почему-то стандартный, давно используемый мною, способ с инъекцией в мок приватного филда не зашел и выдавал null всегда.
    • Юзайте Inversion of Control Principle, например инъекцию зависимости через конструктор или инъекцию через метод с присвоением в конструкторе NullObject. Оба способа будут хороши для тестирования.
    Ответ написан
    1 комментарий
  • Можете дать пример для использование паттерна декоратор?

    Пример на псевдокоде

    UserProvider userProvider = new UserProviderAggregate(
         new LoggerUserProviderDecorator(
              new CacheUserProviderDecorator(
                  new UserProvider(...)
                  new Cache(),
             ),
             new Logger(),
         ),
         new DummyUserProvider()
    )


    все классы UserProviderAggregate, LoggerUserProviderDecorator, CacheUserProviderDecorator, DummyUserProvider и UserProvider реализуют UserProviderInterface... по сути представляют из себя реализации этой абстракции

    Просто каждый передает внутренней зависимости этого же типа (UserProviderInterface) работу внутри своей реализации метода этой абстракции

    public function loadUsers(): Collection<User> {
         return new Collection<User>(...);
    }


    или cache:
    public function loadUsers(): Collection<User> {
         if(cache.has('cache_users_key')) {
            return cache.get('cache_users_key');
        }
    
        Collection<User> users = new Collection<User>(...)
    
        cache.put('cache_users_key', users)
    
        return users;
    }


    или агрегированные:
    public function loadUsers(): Collection<User> {
         if(...any condition) {
             return dbUsers.loadUsers();
         }
    
         if(...any condition) {
             return dummyUsers.loadUsers();
         }
    
         throw new Exception();
    }
    Ответ написан
    Комментировать

Лучшие вопросы пользователя

Все вопросы (51)