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);
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.
*
* TODO: Remove this file when we drop support for PHP < 8.4
*/
// On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
if (PHP_VERSION_ID >= 80400) {
return ['doctrine' => ['orm' => ['enable_native_lazy_objects' => true]]];
}
return [];
return static function(DoctrineConfig $doctrine) {
//On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
if (PHP_VERSION_ID >= 80400) {
$doctrine->orm()->enableNativeLazyObjects(true);
}
};

View file

@ -27,7 +27,6 @@ use App\Doctrine\Functions\SiValueSort;
use App\Exceptions\InvalidRegexException;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
use Pdo\Sqlite;
/**
* This middleware is used to add the regexp operator to the SQLite platform.
@ -45,30 +44,17 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
if ($params['driver'] === 'pdo_sqlite') {
$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) {
$native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC);
$native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
//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->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
//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('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
//Create a new collation for natural sorting
$native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
//Create a function for SI prefix value sorting
$native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC);
}
//Create a function for SI prefix value sorting
$native_connection->sqliteCreateFunction('SI_VALUE', SiValueSort::sqliteSiValue(...), 1, \PDO::SQLITE_DETERMINISTIC);
}
}
@ -132,4 +118,4 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
return $index + 1;
}
}
}

View file

@ -117,7 +117,7 @@ class Project extends AbstractStructuralDBElement
/**
* @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'])]
#[ORM\Column(type: Types::STRING, length: 64, nullable: true)]
protected ?string $status = null;

View file

@ -38,7 +38,7 @@ class AttachmentTypeType extends AbstractType
return StructuralEntityType::class;
}
public function configureOptions(OptionsResolver $resolver): void
public function configureOptions(OptionsResolver $resolver)
{
$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);
foreach($this->getPartLotsForBOMEntry($bom_entry) as $lot) {
//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[$id] = min($remaining_amount, $lot->getAmount());
$remaining_amount -= max(0, $this->withdraw_amounts[$id]);
$this->withdraw_amounts[$lot->getID()] = min($remaining_amount, $lot->getAmount());
$remaining_amount -= max(0, $this->withdraw_amounts[$lot->getID()]);
}
}
}
@ -178,10 +176,6 @@ final class ProjectBuildRequest
{
$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)) {
throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!');
}
@ -198,12 +192,10 @@ final class ProjectBuildRequest
{
if ($lot instanceof PartLot) {
$lot_id = $lot->getID();
} else {
} elseif (is_int($lot)) {
$lot_id = $lot;
}
if ($lot_id === null) {
throw new \InvalidArgumentException('The given lot must have an ID!');
} else {
throw new \InvalidArgumentException('The given lot must be an instance of PartLot or an ID of a PartLot!');
}
$this->withdraw_amounts[$lot_id] = $amount;
@ -304,7 +296,7 @@ final class ProjectBuildRequest
* @param bool $dont_check_quantity
* @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;
return $this;

View file

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

View file

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

View file

@ -4,7 +4,7 @@
<p class="m-0">
<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 %}
({{ entry.oldName }})
{% endif %}
@ -12,4 +12,4 @@
<p class="m-0">
<b>{% trans %}log.collection_deleted.on_collection{% endtrans %}</b>:
{{ log_helper.translate_field(entry.collectionName) }}
</p>
</p>

View file

@ -19,7 +19,7 @@
<div class="carousel-caption text-white">
<div><b>{{ pic.name }}</b></div>
<div>{% if pic.filename %}({{ pic.filename }}) {% endif %}</div>
<div>{{ type_label(pic.element) }}</div>
<div>{{ entity_type_label(pic.element) }}</div>
</div>
</div>
{% endif %}
@ -41,4 +41,4 @@
{% else %}
<img src="{{ asset('img/part_placeholder.svg') }}" class="img-fluid img-thumbnail bg-light mb-2" alt="Part main image" height="300" width="300">
{% endif %}
{% endif %}

View file

@ -22,7 +22,7 @@
<i>({{ timeTravel | format_datetime('short') }})</i>
{% endif %}
{% 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 %}
</span>
<span class="float-end">

View file

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

View file

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

View file

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

View file

@ -77,6 +77,7 @@ final class BuerklinProviderTest extends TestCase
public function testAttributesToParametersParsesUnitsAndValues(): void
{
$method = new \ReflectionMethod(BuerklinProvider::class, 'attributesToParameters');
$method->setAccessible(true);
$features = [
[
@ -126,6 +127,7 @@ final class BuerklinProviderTest extends TestCase
public function testComplianceParameters(): void
{
$method = new \ReflectionMethod(BuerklinProvider::class, 'complianceToParameters');
$method->setAccessible(true);
$product = [
'labelRoHS' => 'Yes',
@ -156,6 +158,7 @@ final class BuerklinProviderTest extends TestCase
public function testImageSelectionPrefersZoomAndDeduplicates(): void
{
$method = new \ReflectionMethod(BuerklinProvider::class, 'getProductImages');
$method->setAccessible(true);
$images = [
['format' => 'product', 'url' => '/img/a.webp'],
@ -173,6 +176,7 @@ final class BuerklinProviderTest extends TestCase
public function testFootprintExtraction(): void
{
$method = new \ReflectionMethod(BuerklinProvider::class, 'getPartDetail');
$method->setAccessible(true);
$product = [
'code' => 'TEST1',
@ -208,6 +212,7 @@ final class BuerklinProviderTest extends TestCase
];
$method = new \ReflectionMethod(BuerklinProvider::class, 'pricesToVendorInfo');
$method->setAccessible(true);
$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->setAccessible(true);
$dto = $method->invoke($this->provider, $detail);
@ -267,13 +273,13 @@ final class BuerklinProviderTest extends TestCase
{
$this->assertSame(['buerklin.com'], $this->provider->getHandledDomains());
}
#[DataProvider('buerklinIdFromUrlProvider')]
public function testGetIDFromURLExtractsId(string $url, ?string $expected): void
{
$this->assertSame($expected, $this->provider->getIDFromURL($url));
}
public static function buerklinIdFromUrlProvider(): \Iterator
{
yield 'de long path' => [

View file

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