mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-13 21:59:34 +00:00
Extract entity functionality into traits and interfaces
Co-authored-by: jbtronics <5410681+jbtronics@users.noreply.github.com>
This commit is contained in:
parent
dcafdbe7f7
commit
ebbfd11545
14 changed files with 1084 additions and 740 deletions
|
|
@ -24,13 +24,11 @@ namespace App\Entity\Attachments;
|
|||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\AttachmentsTrait;
|
||||
use App\Entity\Contracts\HasAttachmentsInterface;
|
||||
use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use App\Repository\AttachmentContainingDBElementRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* @template AT of Attachment
|
||||
|
|
@ -39,83 +37,18 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||
abstract class AttachmentContainingDBElement extends AbstractNamedDBElement implements HasMasterAttachmentInterface, HasAttachmentsInterface
|
||||
{
|
||||
use MasterAttachmentTrait;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Attachment>
|
||||
* @phpstan-var Collection<int, AT>
|
||||
* ORM Mapping is done in subclasses (e.g. Part)
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
protected Collection $attachments;
|
||||
use AttachmentsTrait;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->initializeAttachments();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
$attachments = $this->attachments;
|
||||
$this->attachments = new ArrayCollection();
|
||||
//Set master attachment is needed
|
||||
foreach ($attachments as $attachment) {
|
||||
$clone = clone $attachment;
|
||||
if ($attachment === $this->master_picture_attachment) {
|
||||
$this->setMasterPictureAttachment($clone);
|
||||
}
|
||||
$this->addAttachment($clone);
|
||||
}
|
||||
}
|
||||
$this->cloneAttachments();
|
||||
|
||||
//Parent has to be last call, as it resets the ID
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Gets all attachments associated with this element.
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attachment to this element.
|
||||
*
|
||||
* @param Attachment $attachment Attachment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAttachment(Attachment $attachment): self
|
||||
{
|
||||
//Attachment must be associated with this element
|
||||
$attachment->setElement($this);
|
||||
$this->attachments->add($attachment);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given attachment from this element.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAttachment(Attachment $attachment): self
|
||||
{
|
||||
$this->attachments->removeElement($attachment);
|
||||
|
||||
//Check if this is the master attachment -> remove it from master attachment too, or it can not be deleted from DB...
|
||||
if ($attachment === $this->getMasterPictureAttachment()) {
|
||||
$this->setMasterPictureAttachment(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,9 @@ namespace App\Entity\Base;
|
|||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Contracts\CompanyInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use function is_string;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* This abstract class is used for companies like suppliers or manufacturers.
|
||||
|
|
@ -38,226 +36,15 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
* @extends AbstractPartsContainingDBElement<AT, PT>
|
||||
*/
|
||||
#[ORM\MappedSuperclass]
|
||||
abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
||||
abstract class AbstractCompany extends AbstractPartsContainingDBElement implements CompanyInterface
|
||||
{
|
||||
use CompanyTrait;
|
||||
|
||||
#[Groups(['company:read'])]
|
||||
protected ?\DateTimeImmutable $addedDate = null;
|
||||
#[Groups(['company:read'])]
|
||||
protected ?\DateTimeImmutable $lastModified = null;
|
||||
|
||||
/**
|
||||
* @var string The address of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $address = '';
|
||||
|
||||
/**
|
||||
* @var string The phone number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $phone_number = '';
|
||||
|
||||
/**
|
||||
* @var string The fax number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $fax_number = '';
|
||||
|
||||
/**
|
||||
* @var string The email address of the company
|
||||
*/
|
||||
#[Assert\Email]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $email_address = '';
|
||||
|
||||
/**
|
||||
* @var string The website of the company
|
||||
*/
|
||||
#[Assert\Url(requireTld: false)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
protected string $website = '';
|
||||
|
||||
#[Groups(['company:read', 'company:write', 'import', 'full', 'extended'])]
|
||||
protected string $comment = '';
|
||||
|
||||
/**
|
||||
* @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
protected string $auto_product_url = '';
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Get the address.
|
||||
*
|
||||
* @return string the address of the company (with "\n" as line break)
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the phone number.
|
||||
*
|
||||
* @return string the phone number of the company
|
||||
*/
|
||||
public function getPhoneNumber(): string
|
||||
{
|
||||
return $this->phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fax number.
|
||||
*
|
||||
* @return string the fax number of the company
|
||||
*/
|
||||
public function getFaxNumber(): string
|
||||
{
|
||||
return $this->fax_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the e-mail address.
|
||||
*
|
||||
* @return string the e-mail address of the company
|
||||
*/
|
||||
public function getEmailAddress(): string
|
||||
{
|
||||
return $this->email_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the website.
|
||||
*
|
||||
* @return string the website of the company
|
||||
*/
|
||||
public function getWebsite(): string
|
||||
{
|
||||
return $this->website;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to the website of an article.
|
||||
*
|
||||
* @param string|null $partnr * NULL for returning the URL with a placeholder for the part number
|
||||
* * or the part number for returning the direct URL to the article
|
||||
*
|
||||
* @return string the link to the article
|
||||
*/
|
||||
public function getAutoProductUrl(?string $partnr = null): string
|
||||
{
|
||||
if (is_string($partnr)) {
|
||||
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
||||
}
|
||||
|
||||
return $this->auto_product_url;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Set the addres.
|
||||
*
|
||||
* @param string $new_address the new address (with "\n" as line break)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAddress(string $new_address): self
|
||||
{
|
||||
$this->address = $new_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the phone number.
|
||||
*
|
||||
* @param string $new_phone_number the new phone number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPhoneNumber(string $new_phone_number): self
|
||||
{
|
||||
$this->phone_number = $new_phone_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fax number.
|
||||
*
|
||||
* @param string $new_fax_number the new fax number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaxNumber(string $new_fax_number): self
|
||||
{
|
||||
$this->fax_number = $new_fax_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the e-mail address.
|
||||
*
|
||||
* @param string $new_email_address the new e-mail address
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmailAddress(string $new_email_address): self
|
||||
{
|
||||
$this->email_address = $new_email_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the website.
|
||||
*
|
||||
* @param string $new_website the new website
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWebsite(string $new_website): self
|
||||
{
|
||||
$this->website = $new_website;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the link to the website of an article.
|
||||
*
|
||||
* @param string $new_url the new URL with the placeholder %PARTNUMBER% for the part number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoProductUrl(string $new_url): self
|
||||
{
|
||||
$this->auto_product_url = $new_url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use App\Entity\Attachments\ProjectAttachment;
|
|||
use App\Entity\Attachments\StorageLocationAttachment;
|
||||
use App\Entity\Attachments\SupplierAttachment;
|
||||
use App\Entity\Attachments\UserAttachment;
|
||||
use App\Entity\Contracts\DBElementInterface;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
|
|
@ -56,11 +57,9 @@ use App\Entity\Parts\MeasurementUnit;
|
|||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Repository\DBElementRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JsonSerializable;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* This class is for managing all database objects.
|
||||
|
|
@ -106,36 +105,13 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||
'user' => User::class]
|
||||
)]
|
||||
#[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)]
|
||||
abstract class AbstractDBElement implements JsonSerializable
|
||||
abstract class AbstractDBElement implements JsonSerializable, DBElementInterface
|
||||
{
|
||||
/** @var int|null The Identification number for this part. This value is unique for the element in this table.
|
||||
* Null if the element is not saved to DB yet.
|
||||
*/
|
||||
#[Groups(['full', 'api:basic:read'])]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
protected ?int $id = null;
|
||||
use DBElementTrait;
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
//Set ID to null, so that an new entry is created
|
||||
$this->id = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID. The ID can be zero, or even negative (for virtual elements). If an element is virtual, can be
|
||||
* checked with isVirtualElement().
|
||||
*
|
||||
* Returns null, if the element is not saved to the DB yet.
|
||||
*
|
||||
* @return int|null the ID of this element
|
||||
*/
|
||||
public function getID(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
$this->cloneDBElement();
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
|
|
|
|||
|
|
@ -23,12 +23,9 @@ declare(strict_types=1);
|
|||
namespace App\Entity\Base;
|
||||
|
||||
use App\Repository\NamedDBElementRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Contracts\NamedElementInterface;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* All subclasses of this class have an attribute "name".
|
||||
|
|
@ -38,26 +35,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
abstract class AbstractNamedDBElement extends AbstractDBElement implements NamedElementInterface, TimeStampableInterface, \Stringable
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
/**
|
||||
* @var string The name of this element
|
||||
*/
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $name = '';
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Helpers
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
use NamedElementTrait;
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
|
|
@ -65,40 +43,6 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named
|
|||
//We create a new object, so give it a new creation date
|
||||
$this->addedDate = null;
|
||||
}
|
||||
parent::__clone(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Get the name of this element.
|
||||
*
|
||||
* @return string the name of this element
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
/**
|
||||
* Change the name of this element.
|
||||
*
|
||||
* @param string $new_name the new name
|
||||
*/
|
||||
public function setName(string $new_name): self
|
||||
{
|
||||
$this->name = $new_name;
|
||||
|
||||
return $this;
|
||||
parent::__clone();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,22 +24,18 @@ namespace App\Entity\Base;
|
|||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\Contracts\StructuralElementInterface;
|
||||
use App\Entity\Contracts\HasParametersInterface;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\EntityListeners\TreeCacheInvalidationListener;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||
use App\Entity\Parameters\ParametersTrait;
|
||||
use App\Validator\Constraints\NoneOfItsChildren;
|
||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use function count;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* All elements with the fields "id", "name" and "parent_id" (at least).
|
||||
|
|
@ -62,52 +58,10 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||
#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)]
|
||||
#[ORM\MappedSuperclass(repositoryClass: StructuralDBElementRepository::class)]
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement implements StructuralElementInterface, HasParametersInterface
|
||||
{
|
||||
use ParametersTrait;
|
||||
|
||||
/**
|
||||
* This is a not standard character, so build a const, so a dev can easily use it.
|
||||
*/
|
||||
final public const PATH_DELIMITER_ARROW = ' → ';
|
||||
|
||||
/**
|
||||
* @var string The comment info for this element as markdown
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
protected string $comment = '';
|
||||
|
||||
/**
|
||||
* @var bool If this property is set, this element can not be selected for part properties.
|
||||
* Useful if this element should be used only for grouping, sorting.
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::BOOLEAN)]
|
||||
protected bool $not_selectable = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $level = 0;
|
||||
|
||||
/**
|
||||
* We can not define the mapping here, or we will get an exception. Unfortunately we have to do the mapping in the
|
||||
* subclasses.
|
||||
*
|
||||
* @var Collection<int, AbstractStructuralDBElement>
|
||||
* @phpstan-var Collection<int, static>
|
||||
*/
|
||||
#[Groups(['include_children'])]
|
||||
protected Collection $children;
|
||||
|
||||
/**
|
||||
* @var AbstractStructuralDBElement|null
|
||||
* @phpstan-var static|null
|
||||
*/
|
||||
#[Groups(['include_parents', 'import'])]
|
||||
#[NoneOfItsChildren]
|
||||
protected ?AbstractStructuralDBElement $parent = null;
|
||||
use StructuralElementTrait;
|
||||
|
||||
/**
|
||||
* Mapping done in subclasses.
|
||||
|
|
@ -119,21 +73,10 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
|||
#[UniqueObjectCollection(fields: ['name', 'group', 'element'])]
|
||||
protected Collection $parameters;
|
||||
|
||||
/** @var string[] all names of all parent elements as an array of strings,
|
||||
* the last array element is the name of the element itself
|
||||
*/
|
||||
private array $full_path_strings = [];
|
||||
|
||||
/**
|
||||
* Alternative names (semicolon-separated) for this element, which can be used for searching (especially for info provider system)
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true, options: ['default' => null])]
|
||||
private ?string $alternative_names = "";
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->initializeStructuralElement();
|
||||
$this->parameters = new ArrayCollection();
|
||||
}
|
||||
|
||||
|
|
@ -149,307 +92,4 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
|||
}
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* StructuralDBElement constructor.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Check if this element is a child of another element (recursive).
|
||||
*
|
||||
* @param AbstractStructuralDBElement $another_element the object to compare
|
||||
* IMPORTANT: both objects to compare must be from the same class (for example two "Device" objects)!
|
||||
*
|
||||
* @return bool true, if this element is child of $another_element
|
||||
*
|
||||
* @throws InvalidArgumentException if there was an error
|
||||
*/
|
||||
public function isChildOf(self $another_element): bool
|
||||
{
|
||||
$class_name = static::class;
|
||||
|
||||
//Check if both elements compared, are from the same type
|
||||
// (we have to check inheritance, or we get exceptions when using doctrine entities (they have a proxy type):
|
||||
if (!$another_element instanceof $class_name && !is_a($this, $another_element::class)) {
|
||||
throw new InvalidArgumentException('isChildOf() only works for objects of the same type!');
|
||||
}
|
||||
|
||||
if (!$this->getParent() instanceof self) { // this is the root node
|
||||
return false;
|
||||
}
|
||||
|
||||
//If the parent element is equal to the element we want to compare, return true
|
||||
if ($this->getParent()->getID() === null) {
|
||||
//If the IDs are not yet defined, we have to compare the objects itself
|
||||
if ($this->getParent() === $another_element) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($this->getParent()->getID() === $another_element->getID()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Otherwise, check recursively
|
||||
return $this->parent->isChildOf($another_element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this element is an root element (has no parent).
|
||||
*
|
||||
* @return bool true if this element is a root element
|
||||
*/
|
||||
public function isRoot(): bool
|
||||
{
|
||||
return $this->parent === null;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Get the parent of this element.
|
||||
*
|
||||
* @return static|null The parent element. Null if this element, does not have a parent.
|
||||
*/
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment of the element as markdown encoded string.
|
||||
|
||||
*
|
||||
* @return string the comment
|
||||
*/
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level.
|
||||
*
|
||||
* The level of the root node is -1.
|
||||
*
|
||||
* @return int the level of this element (zero means a most top element
|
||||
* [a sub element of the root node])
|
||||
*/
|
||||
public function getLevel(): int
|
||||
{
|
||||
/*
|
||||
* Only check for nodes that have a parent. In the other cases zero is correct.
|
||||
*/
|
||||
if (0 === $this->level && $this->parent instanceof self) {
|
||||
$element = $this->parent;
|
||||
while ($element instanceof self) {
|
||||
/** @var AbstractStructuralDBElement $element */
|
||||
$element = $element->parent;
|
||||
++$this->level;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path.
|
||||
*
|
||||
* @param string $delimiter the delimiter of the returned string
|
||||
*
|
||||
* @return string the full path (incl. the name of this element), delimited by $delimiter
|
||||
*/
|
||||
#[Groups(['api:basic:read'])]
|
||||
#[SerializedName('full_path')]
|
||||
public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string
|
||||
{
|
||||
if ($this->full_path_strings === []) {
|
||||
$this->full_path_strings = [];
|
||||
$this->full_path_strings[] = $this->getName();
|
||||
$element = $this;
|
||||
|
||||
$overflow = 20; //We only allow 20 levels depth
|
||||
|
||||
while ($element->parent instanceof self && $overflow >= 0) {
|
||||
$element = $element->parent;
|
||||
$this->full_path_strings[] = $element->getName();
|
||||
//Decrement to prevent mem overflow.
|
||||
--$overflow;
|
||||
}
|
||||
|
||||
$this->full_path_strings = array_reverse($this->full_path_strings);
|
||||
}
|
||||
|
||||
return implode($delimiter, $this->full_path_strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to this element (including the element itself).
|
||||
*
|
||||
* @return self[] An array with all (recursively) parent elements (including this one),
|
||||
* ordered from the lowest levels (root node) first to the highest level (the element itself)
|
||||
*/
|
||||
public function getPathArray(): array
|
||||
{
|
||||
$tmp = [];
|
||||
$tmp[] = $this;
|
||||
|
||||
//We only allow 20 levels depth
|
||||
while (!end($tmp)->isRoot() && count($tmp) < 20) {
|
||||
$tmp[] = end($tmp)->parent;
|
||||
}
|
||||
|
||||
return array_reverse($tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sub elements of this element.
|
||||
*
|
||||
* @return Collection<static>|iterable all subelements as an array of objects (sorted by their full path)
|
||||
* @psalm-return Collection<int, static>
|
||||
*/
|
||||
public function getSubelements(): iterable
|
||||
{
|
||||
//If the parent is equal to this object, we would get an endless loop, so just return an empty array
|
||||
//This is just a workaround, as validator should prevent this behaviour, before it gets written to the database
|
||||
if ($this->parent === $this) {
|
||||
return new ArrayCollection();
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
return $this->children ?? new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getSubelements()
|
||||
* @return Collection<static>|iterable
|
||||
* @psalm-return Collection<int, static>
|
||||
*/
|
||||
public function getChildren(): iterable
|
||||
{
|
||||
return $this->getSubelements();
|
||||
}
|
||||
|
||||
public function isNotSelectable(): bool
|
||||
{
|
||||
return $this->not_selectable;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Sets the new parent object.
|
||||
*
|
||||
* @param static|null $new_parent The new parent object
|
||||
* @return $this
|
||||
*/
|
||||
public function setParent(?self $new_parent): self
|
||||
{
|
||||
/*
|
||||
if ($new_parent->isChildOf($this)) {
|
||||
throw new \InvalidArgumentException('You can not use one of the element childs as parent!');
|
||||
} */
|
||||
|
||||
$this->parent = $new_parent;
|
||||
|
||||
//Add this element as child to the new parent
|
||||
if ($new_parent instanceof self) {
|
||||
$new_parent->getChildren()->add($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the comment.
|
||||
*
|
||||
* @param string $new_comment the new comment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setComment(string $new_comment): self
|
||||
{
|
||||
$this->comment = $new_comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given element as child to this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function addChild(self $child): self
|
||||
{
|
||||
$this->children->add($child);
|
||||
//Children get this element as parent
|
||||
$child->setParent($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given element as child from this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function removeChild(self $child): self
|
||||
{
|
||||
$this->children->removeElement($child);
|
||||
//Children has no parent anymore
|
||||
$child->setParent(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractStructuralDBElement
|
||||
*/
|
||||
public function setNotSelectable(bool $not_selectable): self
|
||||
{
|
||||
$this->not_selectable = $not_selectable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearChildren(): self
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of alternative names.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAlternativeNames(): ?string
|
||||
{
|
||||
if ($this->alternative_names === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Remove trailing comma
|
||||
return rtrim($this->alternative_names, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comma separated list of alternative names.
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlternativeNames(?string $new_value): self
|
||||
{
|
||||
//Add a trailing comma, if not already there (makes it easier to find in the database)
|
||||
if (is_string($new_value) && !str_ends_with($new_value, ',')) {
|
||||
$new_value .= ',';
|
||||
}
|
||||
|
||||
$this->alternative_names = $new_value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
109
src/Entity/Base/AttachmentsTrait.php
Normal file
109
src/Entity/Base/AttachmentsTrait.php
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* Trait providing attachments functionality.
|
||||
*/
|
||||
trait AttachmentsTrait
|
||||
{
|
||||
/**
|
||||
* @var Collection<int, Attachment>
|
||||
* ORM Mapping is done in subclasses (e.g. Part)
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
protected Collection $attachments;
|
||||
|
||||
/**
|
||||
* Initialize the attachments collection.
|
||||
*/
|
||||
protected function initializeAttachments(): void
|
||||
{
|
||||
$this->attachments = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all attachments associated with this element.
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attachment to this element.
|
||||
*
|
||||
* @param Attachment $attachment Attachment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAttachment(Attachment $attachment): self
|
||||
{
|
||||
//Attachment must be associated with this element
|
||||
$attachment->setElement($this);
|
||||
$this->attachments->add($attachment);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given attachment from this element.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAttachment(Attachment $attachment): self
|
||||
{
|
||||
$this->attachments->removeElement($attachment);
|
||||
|
||||
//Check if this is the master attachment -> remove it from master attachment too, or it can not be deleted from DB...
|
||||
if (isset($this->master_picture_attachment) && $attachment === $this->master_picture_attachment) {
|
||||
$this->setMasterPictureAttachment(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone helper for attachments - deep clones all attachments.
|
||||
*/
|
||||
protected function cloneAttachments(): void
|
||||
{
|
||||
if (isset($this->id) && $this->id) {
|
||||
$attachments = $this->attachments;
|
||||
$this->attachments = new ArrayCollection();
|
||||
//Set master attachment is needed
|
||||
foreach ($attachments as $attachment) {
|
||||
$clone = clone $attachment;
|
||||
if (isset($this->master_picture_attachment) && $attachment === $this->master_picture_attachment) {
|
||||
$this->setMasterPictureAttachment($clone);
|
||||
}
|
||||
$this->addAttachment($clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
236
src/Entity/Base/CompanyTrait.php
Normal file
236
src/Entity/Base/CompanyTrait.php
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Trait for company-specific fields like address, phone, email, etc.
|
||||
*/
|
||||
trait CompanyTrait
|
||||
{
|
||||
/**
|
||||
* @var string The address of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $address = '';
|
||||
|
||||
/**
|
||||
* @var string The phone number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $phone_number = '';
|
||||
|
||||
/**
|
||||
* @var string The fax number of the company
|
||||
*/
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $fax_number = '';
|
||||
|
||||
/**
|
||||
* @var string The email address of the company
|
||||
*/
|
||||
#[Assert\Email]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $email_address = '';
|
||||
|
||||
/**
|
||||
* @var string The website of the company
|
||||
*/
|
||||
#[Assert\Url(requireTld: false)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
protected string $website = '';
|
||||
|
||||
/**
|
||||
* @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 2048)]
|
||||
#[Assert\Length(max: 2048)]
|
||||
#[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])]
|
||||
protected string $auto_product_url = '';
|
||||
|
||||
/**
|
||||
* Get the address.
|
||||
*
|
||||
* @return string the address of the company (with "\n" as line break)
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the addres.
|
||||
*
|
||||
* @param string $new_address the new address (with "\n" as line break)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAddress(string $new_address): self
|
||||
{
|
||||
$this->address = $new_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the phone number.
|
||||
*
|
||||
* @return string the phone number of the company
|
||||
*/
|
||||
public function getPhoneNumber(): string
|
||||
{
|
||||
return $this->phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the phone number.
|
||||
*
|
||||
* @param string $new_phone_number the new phone number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPhoneNumber(string $new_phone_number): self
|
||||
{
|
||||
$this->phone_number = $new_phone_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fax number.
|
||||
*
|
||||
* @return string the fax number of the company
|
||||
*/
|
||||
public function getFaxNumber(): string
|
||||
{
|
||||
return $this->fax_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fax number.
|
||||
*
|
||||
* @param string $new_fax_number the new fax number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaxNumber(string $new_fax_number): self
|
||||
{
|
||||
$this->fax_number = $new_fax_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the e-mail address.
|
||||
*
|
||||
* @return string the e-mail address of the company
|
||||
*/
|
||||
public function getEmailAddress(): string
|
||||
{
|
||||
return $this->email_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the e-mail address.
|
||||
*
|
||||
* @param string $new_email_address the new e-mail address
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmailAddress(string $new_email_address): self
|
||||
{
|
||||
$this->email_address = $new_email_address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the website.
|
||||
*
|
||||
* @return string the website of the company
|
||||
*/
|
||||
public function getWebsite(): string
|
||||
{
|
||||
return $this->website;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the website.
|
||||
*
|
||||
* @param string $new_website the new website
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWebsite(string $new_website): self
|
||||
{
|
||||
$this->website = $new_website;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to the website of an article.
|
||||
*
|
||||
* @param string|null $partnr * NULL for returning the URL with a placeholder for the part number
|
||||
* * or the part number for returning the direct URL to the article
|
||||
*
|
||||
* @return string the link to the article
|
||||
*/
|
||||
public function getAutoProductUrl(?string $partnr = null): string
|
||||
{
|
||||
if (is_string($partnr)) {
|
||||
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
||||
}
|
||||
|
||||
return $this->auto_product_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the link to the website of an article.
|
||||
*
|
||||
* @param string $new_url the new URL with the placeholder %PARTNUMBER% for the part number
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoProductUrl(string $new_url): self
|
||||
{
|
||||
$this->auto_product_url = $new_url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
67
src/Entity/Base/DBElementTrait.php
Normal file
67
src/Entity/Base/DBElementTrait.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* Trait providing basic database element functionality with an ID.
|
||||
*/
|
||||
trait DBElementTrait
|
||||
{
|
||||
/**
|
||||
* @var int|null The Identification number for this element. This value is unique for the element in this table.
|
||||
* Null if the element is not saved to DB yet.
|
||||
*/
|
||||
#[Groups(['full', 'api:basic:read'])]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
protected ?int $id = null;
|
||||
|
||||
/**
|
||||
* Get the ID. The ID can be zero, or even negative (for virtual elements). If an element is virtual, can be
|
||||
* checked with isVirtualElement().
|
||||
*
|
||||
* Returns null, if the element is not saved to the DB yet.
|
||||
*
|
||||
* @return int|null the ID of this element
|
||||
*/
|
||||
public function getID(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone helper for DB element - resets ID on clone.
|
||||
*/
|
||||
protected function cloneDBElement(): void
|
||||
{
|
||||
if ($this->id) {
|
||||
//Set ID to null, so that a new entry is created
|
||||
$this->id = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/Entity/Base/NamedElementTrait.php
Normal file
73
src/Entity/Base/NamedElementTrait.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Trait providing named element functionality.
|
||||
*/
|
||||
trait NamedElementTrait
|
||||
{
|
||||
/**
|
||||
* @var string The name of this element
|
||||
*/
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
* Get the name of this element.
|
||||
*
|
||||
* @return string the name of this element
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the name of this element.
|
||||
*
|
||||
* @param string $new_name the new name
|
||||
*/
|
||||
public function setName(string $new_name): self
|
||||
{
|
||||
$this->name = $new_name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* String representation returns the name.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
}
|
||||
376
src/Entity/Base/StructuralElementTrait.php
Normal file
376
src/Entity/Base/StructuralElementTrait.php
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use App\Validator\Constraints\NoneOfItsChildren;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Trait for structural/hierarchical elements forming a tree structure.
|
||||
*/
|
||||
trait StructuralElementTrait
|
||||
{
|
||||
/**
|
||||
* This is a not standard character, so build a const, so a dev can easily use it.
|
||||
*/
|
||||
final public const PATH_DELIMITER_ARROW = ' → ';
|
||||
|
||||
/**
|
||||
* @var string The comment info for this element as markdown
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
protected string $comment = '';
|
||||
|
||||
/**
|
||||
* @var bool If this property is set, this element can not be selected for part properties.
|
||||
* Useful if this element should be used only for grouping, sorting.
|
||||
*/
|
||||
#[Groups(['full', 'import'])]
|
||||
#[ORM\Column(type: Types::BOOLEAN)]
|
||||
protected bool $not_selectable = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $level = 0;
|
||||
|
||||
/**
|
||||
* We can not define the mapping here, or we will get an exception. Unfortunately we have to do the mapping in the
|
||||
* subclasses.
|
||||
*
|
||||
* @var Collection<int, static>
|
||||
*/
|
||||
#[Groups(['include_children'])]
|
||||
protected Collection $children;
|
||||
|
||||
/**
|
||||
* @var static|null
|
||||
*/
|
||||
#[Groups(['include_parents', 'import'])]
|
||||
#[NoneOfItsChildren]
|
||||
protected ?self $parent = null;
|
||||
|
||||
/** @var string[] all names of all parent elements as an array of strings,
|
||||
* the last array element is the name of the element itself
|
||||
*/
|
||||
private array $full_path_strings = [];
|
||||
|
||||
/**
|
||||
* Alternative names (semicolon-separated) for this element, which can be used for searching (especially for info provider system)
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true, options: ['default' => null])]
|
||||
private ?string $alternative_names = "";
|
||||
|
||||
/**
|
||||
* Initialize structural element collections.
|
||||
*/
|
||||
protected function initializeStructuralElement(): void
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this element is a child of another element (recursive).
|
||||
*
|
||||
* @param self $another_element the object to compare
|
||||
* IMPORTANT: both objects to compare must be from the same class (for example two "Device" objects)!
|
||||
*
|
||||
* @return bool true, if this element is child of $another_element
|
||||
*
|
||||
* @throws InvalidArgumentException if there was an error
|
||||
*/
|
||||
public function isChildOf(self $another_element): bool
|
||||
{
|
||||
$class_name = static::class;
|
||||
|
||||
//Check if both elements compared, are from the same type
|
||||
// (we have to check inheritance, or we get exceptions when using doctrine entities (they have a proxy type):
|
||||
if (!$another_element instanceof $class_name && !is_a($this, $another_element::class)) {
|
||||
throw new InvalidArgumentException('isChildOf() only works for objects of the same type!');
|
||||
}
|
||||
|
||||
if (!$this->getParent() instanceof self) { // this is the root node
|
||||
return false;
|
||||
}
|
||||
|
||||
//If the parent element is equal to the element we want to compare, return true
|
||||
if ($this->getParent()->getID() === null) {
|
||||
//If the IDs are not yet defined, we have to compare the objects itself
|
||||
if ($this->getParent() === $another_element) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($this->getParent()->getID() === $another_element->getID()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Otherwise, check recursively
|
||||
return $this->parent->isChildOf($another_element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this element is a root element (has no parent).
|
||||
*
|
||||
* @return bool true if this element is a root element
|
||||
*/
|
||||
public function isRoot(): bool
|
||||
{
|
||||
return $this->parent === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent of this element.
|
||||
*
|
||||
* @return static|null The parent element. Null if this element, does not have a parent.
|
||||
*/
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment of the element as markdown encoded string.
|
||||
*
|
||||
* @return string the comment
|
||||
*/
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the comment.
|
||||
*
|
||||
* @param string $new_comment the new comment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setComment(string $new_comment): self
|
||||
{
|
||||
$this->comment = $new_comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level.
|
||||
*
|
||||
* The level of the root node is -1.
|
||||
*
|
||||
* @return int the level of this element (zero means a most top element
|
||||
* [a sub element of the root node])
|
||||
*/
|
||||
public function getLevel(): int
|
||||
{
|
||||
/*
|
||||
* Only check for nodes that have a parent. In the other cases zero is correct.
|
||||
*/
|
||||
if (0 === $this->level && $this->parent instanceof self) {
|
||||
$element = $this->parent;
|
||||
while ($element instanceof self) {
|
||||
$element = $element->parent;
|
||||
++$this->level;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path.
|
||||
*
|
||||
* @param string $delimiter the delimiter of the returned string
|
||||
*
|
||||
* @return string the full path (incl. the name of this element), delimited by $delimiter
|
||||
*/
|
||||
#[Groups(['api:basic:read'])]
|
||||
#[SerializedName('full_path')]
|
||||
public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string
|
||||
{
|
||||
if ($this->full_path_strings === []) {
|
||||
$this->full_path_strings = [];
|
||||
$this->full_path_strings[] = $this->getName();
|
||||
$element = $this;
|
||||
|
||||
$overflow = 20; //We only allow 20 levels depth
|
||||
|
||||
while ($element->parent instanceof self && $overflow >= 0) {
|
||||
$element = $element->parent;
|
||||
$this->full_path_strings[] = $element->getName();
|
||||
//Decrement to prevent mem overflow.
|
||||
--$overflow;
|
||||
}
|
||||
|
||||
$this->full_path_strings = array_reverse($this->full_path_strings);
|
||||
}
|
||||
|
||||
return implode($delimiter, $this->full_path_strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to this element (including the element itself).
|
||||
*
|
||||
* @return self[] An array with all (recursively) parent elements (including this one),
|
||||
* ordered from the lowest levels (root node) first to the highest level (the element itself)
|
||||
*/
|
||||
public function getPathArray(): array
|
||||
{
|
||||
$tmp = [];
|
||||
$tmp[] = $this;
|
||||
|
||||
//We only allow 20 levels depth
|
||||
while (!end($tmp)->isRoot() && count($tmp) < 20) {
|
||||
$tmp[] = end($tmp)->parent;
|
||||
}
|
||||
|
||||
return array_reverse($tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sub elements of this element.
|
||||
*
|
||||
* @return Collection<static>|iterable all subelements as an array of objects (sorted by their full path)
|
||||
*/
|
||||
public function getSubelements(): iterable
|
||||
{
|
||||
//If the parent is equal to this object, we would get an endless loop, so just return an empty array
|
||||
//This is just a workaround, as validator should prevent this behaviour, before it gets written to the database
|
||||
if ($this->parent === $this) {
|
||||
return new ArrayCollection();
|
||||
}
|
||||
|
||||
return $this->children ?? new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getSubelements()
|
||||
* @return Collection<static>|iterable
|
||||
*/
|
||||
public function getChildren(): iterable
|
||||
{
|
||||
return $this->getSubelements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new parent object.
|
||||
*
|
||||
* @param static|null $new_parent The new parent object
|
||||
* @return $this
|
||||
*/
|
||||
public function setParent(?self $new_parent): self
|
||||
{
|
||||
$this->parent = $new_parent;
|
||||
|
||||
//Add this element as child to the new parent
|
||||
if ($new_parent instanceof self) {
|
||||
$new_parent->getChildren()->add($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given element as child to this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function addChild(self $child): self
|
||||
{
|
||||
$this->children->add($child);
|
||||
//Children get this element as parent
|
||||
$child->setParent($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given element as child from this element.
|
||||
* @param static $child
|
||||
* @return $this
|
||||
*/
|
||||
public function removeChild(self $child): self
|
||||
{
|
||||
$this->children->removeElement($child);
|
||||
//Children has no parent anymore
|
||||
$child->setParent(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNotSelectable(): bool
|
||||
{
|
||||
return $this->not_selectable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setNotSelectable(bool $not_selectable): self
|
||||
{
|
||||
$this->not_selectable = $not_selectable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearChildren(): self
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of alternative names.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAlternativeNames(): ?string
|
||||
{
|
||||
if ($this->alternative_names === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Remove trailing comma
|
||||
return rtrim($this->alternative_names, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comma separated list of alternative names.
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlternativeNames(?string $new_value): self
|
||||
{
|
||||
//Add a trailing comma, if not already there (makes it easier to find in the database)
|
||||
if (is_string($new_value) && !str_ends_with($new_value, ',')) {
|
||||
$new_value .= ',';
|
||||
}
|
||||
|
||||
$this->alternative_names = $new_value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
57
src/Entity/Contracts/CompanyInterface.php
Normal file
57
src/Entity/Contracts/CompanyInterface.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for company entities (suppliers, manufacturers).
|
||||
*/
|
||||
interface CompanyInterface
|
||||
{
|
||||
/**
|
||||
* Get the address.
|
||||
*
|
||||
* @return string the address of the company (with "\n" as line break)
|
||||
*/
|
||||
public function getAddress(): string;
|
||||
|
||||
/**
|
||||
* Get the phone number.
|
||||
*
|
||||
* @return string the phone number of the company
|
||||
*/
|
||||
public function getPhoneNumber(): string;
|
||||
|
||||
/**
|
||||
* Get the e-mail address.
|
||||
*
|
||||
* @return string the e-mail address of the company
|
||||
*/
|
||||
public function getEmailAddress(): string;
|
||||
|
||||
/**
|
||||
* Get the website.
|
||||
*
|
||||
* @return string the website of the company
|
||||
*/
|
||||
public function getWebsite(): string;
|
||||
}
|
||||
38
src/Entity/Contracts/DBElementInterface.php
Normal file
38
src/Entity/Contracts/DBElementInterface.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for entities that have a database ID.
|
||||
*/
|
||||
interface DBElementInterface
|
||||
{
|
||||
/**
|
||||
* Get the ID. The ID can be zero, or even negative (for virtual elements).
|
||||
*
|
||||
* Returns null, if the element is not saved to the DB yet.
|
||||
*
|
||||
* @return int|null the ID of this element
|
||||
*/
|
||||
public function getID(): ?int;
|
||||
}
|
||||
38
src/Entity/Contracts/HasParametersInterface.php
Normal file
38
src/Entity/Contracts/HasParametersInterface.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* Interface for entities that have parameters.
|
||||
*/
|
||||
interface HasParametersInterface
|
||||
{
|
||||
/**
|
||||
* Return all associated parameters.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getParameters(): Collection;
|
||||
}
|
||||
70
src/Entity/Contracts/StructuralElementInterface.php
Normal file
70
src/Entity/Contracts/StructuralElementInterface.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Contracts;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* Interface for structural elements that form a tree hierarchy.
|
||||
*/
|
||||
interface StructuralElementInterface
|
||||
{
|
||||
/**
|
||||
* Get the parent of this element.
|
||||
*
|
||||
* @return static|null The parent element. Null if this element does not have a parent.
|
||||
*/
|
||||
public function getParent(): ?self;
|
||||
|
||||
/**
|
||||
* Get all sub elements of this element.
|
||||
*
|
||||
* @return Collection<static>|iterable all subelements
|
||||
*/
|
||||
public function getChildren(): iterable;
|
||||
|
||||
/**
|
||||
* Checks if this element is a root element (has no parent).
|
||||
*
|
||||
* @return bool true if this element is a root element
|
||||
*/
|
||||
public function isRoot(): bool;
|
||||
|
||||
/**
|
||||
* Get the full path.
|
||||
*
|
||||
* @param string $delimiter the delimiter of the returned string
|
||||
*
|
||||
* @return string the full path (incl. the name of this element), delimited by $delimiter
|
||||
*/
|
||||
public function getFullPath(string $delimiter = ' → '): string;
|
||||
|
||||
/**
|
||||
* Get the level.
|
||||
*
|
||||
* The level of the root node is -1.
|
||||
*
|
||||
* @return int the level of this element (zero means a most top element)
|
||||
*/
|
||||
public function getLevel(): int;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue