Compare commits

..

No commits in common. "188444b30f92cdd3990d5c72b53673e28a1f3cbc" and "5e18ae28740122ba765babc7a1953addf18ba59f" have entirely different histories.

16 changed files with 69 additions and 63 deletions

View file

@ -0,0 +1,16 @@
services:
EasyCorp\EasyLog\EasyLogHandler:
public: false
arguments: ['%kernel.logs_dir%/%kernel.environment%.log']
#// FIXME: How to add this configuration automatically without messing up with the monolog configuration?
#monolog:
# handlers:
# buffered:
# type: buffer
# handler: easylog
# channels: ['!event']
# level: debug
# easylog:
# type: service
# id: EasyCorp\EasyLog\EasyLogHandler

View file

@ -20,16 +20,16 @@
declare(strict_types=1); declare(strict_types=1);
use Symfony\Config\DoctrineConfig;
/** /**
* This file enables native lazy objects on PHP 8.4+. * This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+.
* We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version. * We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version.
*
* TODO: Remove this file when we drop support for PHP < 8.4
*/ */
return static function(DoctrineConfig $doctrine) {
//On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies. //On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
if (PHP_VERSION_ID >= 80400) { if (PHP_VERSION_ID >= 80400) {
return ['doctrine' => ['orm' => ['enable_native_lazy_objects' => true]]]; $doctrine->orm()->enableNativeLazyObjects(true);
} }
};
return [];

View file

@ -27,7 +27,6 @@ use App\Doctrine\Functions\SiValueSort;
use App\Exceptions\InvalidRegexException; use App\Exceptions\InvalidRegexException;
use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
use Pdo\Sqlite;
/** /**
* This middleware is used to add the regexp operator to the SQLite platform. * This middleware is used to add the regexp operator to the SQLite platform.
@ -45,20 +44,8 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
if ($params['driver'] === 'pdo_sqlite') { if ($params['driver'] === 'pdo_sqlite') {
$native_connection = $connection->getNativeConnection(); $native_connection = $connection->getNativeConnection();
//Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
if($native_connection instanceof \PDO) { if($native_connection instanceof \PDO) {
//Use the new PDO::createFunction and PDO::createCollation methods if available (PHP 8.4+)
if (is_a($native_connection, Sqlite::class)) { #TODO: Remove this check when PHP 8.4 is the minimum requirement
$native_connection->createFunction('REGEXP', self::regexp(...), 2, Sqlite::DETERMINISTIC);
$native_connection->createFunction('FIELD', self::field(...), -1, Sqlite::DETERMINISTIC);
$native_connection->createFunction('FIELD2', self::field2(...), 2, Sqlite::DETERMINISTIC);
//Create a new collation for natural sorting
$native_connection->createCollation('NATURAL_CMP', strnatcmp(...));
//Create a function for SI prefix value sorting
$native_connection->createFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, Sqlite::DETERMINISTIC);
} else {
$native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC);
$native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
@ -70,7 +57,6 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
$native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC);
} }
} }
}
return $connection; return $connection;

View file

@ -117,7 +117,7 @@ class Project extends AbstractStructuralDBElement
/** /**
* @var string|null The current status of the project * @var string|null The current status of the project
*/ */
#[Assert\Choice(choices: ['draft', 'planning', 'in_production', 'finished', 'archived'])] #[Assert\Choice(['draft', 'planning', 'in_production', 'finished', 'archived'])]
#[Groups(['extended', 'full', 'project:read', 'project:write', 'import'])] #[Groups(['extended', 'full', 'project:read', 'project:write', 'import'])]
#[ORM\Column(type: Types::STRING, length: 64, nullable: true)] #[ORM\Column(type: Types::STRING, length: 64, nullable: true)]
protected ?string $status = null; protected ?string $status = null;

View file

@ -38,7 +38,7 @@ class AttachmentTypeType extends AbstractType
return StructuralEntityType::class; return StructuralEntityType::class;
} }
public function configureOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver)
{ {
$resolver->define('attachment_filter_class')->allowedTypes('null', 'string')->default(null); $resolver->define('attachment_filter_class')->allowedTypes('null', 'string')->default(null);

View file

@ -84,10 +84,8 @@ final class ProjectBuildRequest
$remaining_amount = $this->getNeededAmountForBOMEntry($bom_entry); $remaining_amount = $this->getNeededAmountForBOMEntry($bom_entry);
foreach($this->getPartLotsForBOMEntry($bom_entry) as $lot) { foreach($this->getPartLotsForBOMEntry($bom_entry) as $lot) {
//If the lot has instock use it for the build //If the lot has instock use it for the build
$id = $lot->getID() ?? throw new \RuntimeException("Part lot needs to have an ID!"); $this->withdraw_amounts[$lot->getID()] = min($remaining_amount, $lot->getAmount());
$remaining_amount -= max(0, $this->withdraw_amounts[$lot->getID()]);
$this->withdraw_amounts[$id] = min($remaining_amount, $lot->getAmount());
$remaining_amount -= max(0, $this->withdraw_amounts[$id]);
} }
} }
} }
@ -178,10 +176,6 @@ final class ProjectBuildRequest
{ {
$lot_id = $lot instanceof PartLot ? $lot->getID() : $lot; $lot_id = $lot instanceof PartLot ? $lot->getID() : $lot;
if ($lot_id === null) {
throw new \InvalidArgumentException('The given lot must have an ID!');
}
if (! array_key_exists($lot_id, $this->withdraw_amounts)) { if (! array_key_exists($lot_id, $this->withdraw_amounts)) {
throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!'); throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!');
} }
@ -198,12 +192,10 @@ final class ProjectBuildRequest
{ {
if ($lot instanceof PartLot) { if ($lot instanceof PartLot) {
$lot_id = $lot->getID(); $lot_id = $lot->getID();
} else { } elseif (is_int($lot)) {
$lot_id = $lot; $lot_id = $lot;
} } else {
throw new \InvalidArgumentException('The given lot must be an instance of PartLot or an ID of a PartLot!');
if ($lot_id === null) {
throw new \InvalidArgumentException('The given lot must have an ID!');
} }
$this->withdraw_amounts[$lot_id] = $amount; $this->withdraw_amounts[$lot_id] = $amount;
@ -304,7 +296,7 @@ final class ProjectBuildRequest
* @param bool $dont_check_quantity * @param bool $dont_check_quantity
* @return $this * @return $this
*/ */
public function setDontCheckQuantity(bool $dont_check_quantity): self public function setDontCheckQuantity(bool $dont_check_quantity): ProjectBuildRequest
{ {
$this->dont_check_quantity = $dont_check_quantity; $this->dont_check_quantity = $dont_check_quantity;
return $this; return $this;

View file

@ -158,6 +158,7 @@ class DBElementRepository extends EntityRepository
{ {
$reflection = new ReflectionClass($element::class); $reflection = new ReflectionClass($element::class);
$property = $reflection->getProperty($field); $property = $reflection->getProperty($field);
$property->setAccessible(true);
$property->setValue($element, $new_value); $property->setValue($element, $new_value);
} }
} }

View file

@ -233,6 +233,7 @@ trait PKImportHelperTrait
$reflectionClass = new \ReflectionClass($entity); $reflectionClass = new \ReflectionClass($entity);
$property = $reflectionClass->getProperty('addedDate'); $property = $reflectionClass->getProperty('addedDate');
$property->setAccessible(true);
$property->setValue($entity, $date); $property->setValue($entity, $date);
} }

View file

@ -4,7 +4,7 @@
<p class="m-0"> <p class="m-0">
<b>{% trans %}log.collection_deleted.deleted{% endtrans %}</b>: <b>{% trans %}log.collection_deleted.deleted{% endtrans %}</b>:
{{ type_label(entry.deletedElementClass) }} #{{ entry.deletedElementID }} {{ entity_type_label(entry.deletedElementClass) }} #{{ entry.deletedElementID }}
{% if entry.oldName is not empty %} {% if entry.oldName is not empty %}
({{ entry.oldName }}) ({{ entry.oldName }})
{% endif %} {% endif %}

View file

@ -19,7 +19,7 @@
<div class="carousel-caption text-white"> <div class="carousel-caption text-white">
<div><b>{{ pic.name }}</b></div> <div><b>{{ pic.name }}</b></div>
<div>{% if pic.filename %}({{ pic.filename }}) {% endif %}</div> <div>{% if pic.filename %}({{ pic.filename }}) {% endif %}</div>
<div>{{ type_label(pic.element) }}</div> <div>{{ entity_type_label(pic.element) }}</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View file

@ -22,7 +22,7 @@
<i>({{ timeTravel | format_datetime('short') }})</i> <i>({{ timeTravel | format_datetime('short') }})</i>
{% endif %} {% endif %}
{% if part.projectBuildPart %} {% if part.projectBuildPart %}
(<i>{{ type_label(part.builtProject) }}</i>: <a class="text-white" href="{{ entity_url(part.builtProject) }}">{{ part.builtProject.name }}</a>) (<i>{{ entity_type_label(part.builtProject) }}</i>: <a class="text-white" href="{{ entity_url(part.builtProject) }}">{{ part.builtProject.name }}</a>)
{% endif %} {% endif %}
</span> </span>
<span class="float-end"> <span class="float-end">

View file

@ -26,7 +26,6 @@ use App\Doctrine\Functions\SiValueSort;
use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Platforms\SQLitePlatform;
use PHPUnit\Framework\Attributes\DataProvider;
final class SiValueSortTest extends AbstractDoctrineFunctionTestCase final class SiValueSortTest extends AbstractDoctrineFunctionTestCase
{ {
@ -72,7 +71,9 @@ final class SiValueSortTest extends AbstractDoctrineFunctionTestCase
$this->assertSame('SI_VALUE(part_name)', $sql); $this->assertSame('SI_VALUE(part_name)', $sql);
} }
#[DataProvider('sqliteSiValueProvider')] /**
* @dataProvider sqliteSiValueProvider
*/
public function testSqliteSiValue(?string $input, ?float $expected): void public function testSqliteSiValue(?string $input, ?float $expected): void
{ {
$result = SiValueSort::sqliteSiValue($input); $result = SiValueSort::sqliteSiValue($input);

View file

@ -276,6 +276,7 @@ final class AttachmentTest extends TestCase
{ {
$reflection = new ReflectionClass($object); $reflection = new ReflectionClass($object);
$reflection_property = $reflection->getProperty($property); $reflection_property = $reflection->getProperty($property);
$reflection_property->setAccessible(true);
$reflection_property->setValue($object, $value); $reflection_property->setValue($object, $value);
} }

View file

@ -82,12 +82,7 @@ final class ProjectBuildRequestTest extends TestCase
$part2->setName('Part 2'); $part2->setName('Part 2');
$part2->setPartUnit($float_unit); $part2->setPartUnit($float_unit);
$this->lot2 = new class extends PartLot { $this->lot2 = new PartLot();
public function getID(): ?int
{
return 3;
}
};;
$part2->addPartLot($this->lot2); $part2->addPartLot($this->lot2);
$this->lot2->setAmount(2.5); $this->lot2->setAmount(2.5);
$this->lot2->setDescription('Lot 2'); $this->lot2->setDescription('Lot 2');

View file

@ -77,6 +77,7 @@ final class BuerklinProviderTest extends TestCase
public function testAttributesToParametersParsesUnitsAndValues(): void public function testAttributesToParametersParsesUnitsAndValues(): void
{ {
$method = new \ReflectionMethod(BuerklinProvider::class, 'attributesToParameters'); $method = new \ReflectionMethod(BuerklinProvider::class, 'attributesToParameters');
$method->setAccessible(true);
$features = [ $features = [
[ [
@ -126,6 +127,7 @@ final class BuerklinProviderTest extends TestCase
public function testComplianceParameters(): void public function testComplianceParameters(): void
{ {
$method = new \ReflectionMethod(BuerklinProvider::class, 'complianceToParameters'); $method = new \ReflectionMethod(BuerklinProvider::class, 'complianceToParameters');
$method->setAccessible(true);
$product = [ $product = [
'labelRoHS' => 'Yes', 'labelRoHS' => 'Yes',
@ -156,6 +158,7 @@ final class BuerklinProviderTest extends TestCase
public function testImageSelectionPrefersZoomAndDeduplicates(): void public function testImageSelectionPrefersZoomAndDeduplicates(): void
{ {
$method = new \ReflectionMethod(BuerklinProvider::class, 'getProductImages'); $method = new \ReflectionMethod(BuerklinProvider::class, 'getProductImages');
$method->setAccessible(true);
$images = [ $images = [
['format' => 'product', 'url' => '/img/a.webp'], ['format' => 'product', 'url' => '/img/a.webp'],
@ -173,6 +176,7 @@ final class BuerklinProviderTest extends TestCase
public function testFootprintExtraction(): void public function testFootprintExtraction(): void
{ {
$method = new \ReflectionMethod(BuerklinProvider::class, 'getPartDetail'); $method = new \ReflectionMethod(BuerklinProvider::class, 'getPartDetail');
$method->setAccessible(true);
$product = [ $product = [
'code' => 'TEST1', 'code' => 'TEST1',
@ -208,6 +212,7 @@ final class BuerklinProviderTest extends TestCase
]; ];
$method = new \ReflectionMethod(BuerklinProvider::class, 'pricesToVendorInfo'); $method = new \ReflectionMethod(BuerklinProvider::class, 'pricesToVendorInfo');
$method->setAccessible(true);
$vendorInfo = $method->invoke($this->provider, 'SKU1', 'https://x', $detailPrice); $vendorInfo = $method->invoke($this->provider, 'SKU1', 'https://x', $detailPrice);
@ -255,6 +260,7 @@ final class BuerklinProviderTest extends TestCase
); );
$method = new \ReflectionMethod(BuerklinProvider::class, 'convertPartDetailToSearchResult'); $method = new \ReflectionMethod(BuerklinProvider::class, 'convertPartDetailToSearchResult');
$method->setAccessible(true);
$dto = $method->invoke($this->provider, $detail); $dto = $method->invoke($this->provider, $detail);

View file

@ -367,6 +367,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('sanitizeField'); $method = $reflection->getMethod('sanitizeField');
$method->setAccessible(true);
$this->assertNull($method->invokeArgs($this->provider, [null])); $this->assertNull($method->invokeArgs($this->provider, [null]));
$this->assertEquals('Clean text', $method->invokeArgs($this->provider, ['Clean text'])); $this->assertEquals('Clean text', $method->invokeArgs($this->provider, ['Clean text']));
@ -377,6 +378,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('getUsedCurrency'); $method = $reflection->getMethod('getUsedCurrency');
$method->setAccessible(true);
$this->assertEquals('USD', $method->invokeArgs($this->provider, ['US$'])); $this->assertEquals('USD', $method->invokeArgs($this->provider, ['US$']));
$this->assertEquals('USD', $method->invokeArgs($this->provider, ['$'])); $this->assertEquals('USD', $method->invokeArgs($this->provider, ['$']));
@ -389,6 +391,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('getProductShortURL'); $method = $reflection->getMethod('getProductShortURL');
$method->setAccessible(true);
$result = $method->invokeArgs($this->provider, ['C123456']); $result = $method->invokeArgs($this->provider, ['C123456']);
$this->assertEquals('https://www.lcsc.com/product-detail/C123456.html', $result); $this->assertEquals('https://www.lcsc.com/product-detail/C123456.html', $result);
@ -398,6 +401,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('getProductDatasheets'); $method = $reflection->getMethod('getProductDatasheets');
$method->setAccessible(true);
$result = $method->invokeArgs($this->provider, [null]); $result = $method->invokeArgs($this->provider, [null]);
$this->assertIsArray($result); $this->assertIsArray($result);
@ -413,6 +417,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('getProductImages'); $method = $reflection->getMethod('getProductImages');
$method->setAccessible(true);
$result = $method->invokeArgs($this->provider, [null]); $result = $method->invokeArgs($this->provider, [null]);
$this->assertIsArray($result); $this->assertIsArray($result);
@ -429,6 +434,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('attributesToParameters'); $method = $reflection->getMethod('attributesToParameters');
$method->setAccessible(true);
$attributes = [ $attributes = [
['paramNameEn' => 'Resistance', 'paramValueEn' => '1kΩ'], ['paramNameEn' => 'Resistance', 'paramValueEn' => '1kΩ'],
@ -448,6 +454,7 @@ final class LCSCProviderTest extends TestCase
{ {
$reflection = new \ReflectionClass($this->provider); $reflection = new \ReflectionClass($this->provider);
$method = $reflection->getMethod('pricesToVendorInfo'); $method = $reflection->getMethod('pricesToVendorInfo');
$method->setAccessible(true);
$prices = [ $prices = [
['ladder' => 1, 'productPrice' => '0.10', 'currencySymbol' => 'US$'], ['ladder' => 1, 'productPrice' => '0.10', 'currencySymbol' => 'US$'],