Add Sockets as a Meta Item

This commit is contained in:
Timothy Warren 2022-10-20 11:07:27 -04:00
parent 827ee8a8eb
commit 3777b88800
17 changed files with 449 additions and 51 deletions

24
composer.lock generated
View File

@ -853,23 +853,23 @@
}, },
{ {
"name": "doctrine/inflector", "name": "doctrine/inflector",
"version": "2.0.5", "version": "2.0.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/inflector.git", "url": "https://github.com/doctrine/inflector.git",
"reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392" "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024",
"reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0" "php": "^7.2 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^9", "doctrine/coding-standard": "^10",
"phpstan/phpstan": "^1.8", "phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-strict-rules": "^1.3", "phpstan/phpstan-strict-rules": "^1.3",
@ -924,7 +924,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/inflector/issues", "issues": "https://github.com/doctrine/inflector/issues",
"source": "https://github.com/doctrine/inflector/tree/2.0.5" "source": "https://github.com/doctrine/inflector/tree/2.0.6"
}, },
"funding": [ "funding": [
{ {
@ -940,7 +940,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-09-07T09:01:28+00:00" "time": "2022-10-20T09:10:12+00:00"
}, },
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
@ -6472,12 +6472,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git", "url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "c8e297691dcc30deef58efddd6765c8b7821be56" "reference": "4ed057f00e70bf1a45434fbc6fe14790684ff3c5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/c8e297691dcc30deef58efddd6765c8b7821be56", "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/4ed057f00e70bf1a45434fbc6fe14790684ff3c5",
"reference": "c8e297691dcc30deef58efddd6765c8b7821be56", "reference": "4ed057f00e70bf1a45434fbc6fe14790684ff3c5",
"shasum": "" "shasum": ""
}, },
"conflict": { "conflict": {
@ -6750,6 +6750,7 @@
"phpmailer/phpmailer": "<6.5", "phpmailer/phpmailer": "<6.5",
"phpmussel/phpmussel": ">=1,<1.6", "phpmussel/phpmussel": ">=1,<1.6",
"phpmyadmin/phpmyadmin": "<5.1.3", "phpmyadmin/phpmyadmin": "<5.1.3",
"phpmyfaq/phpmyfaq": "<=3.1.7",
"phpoffice/phpexcel": "<1.8", "phpoffice/phpexcel": "<1.8",
"phpoffice/phpspreadsheet": "<1.16", "phpoffice/phpspreadsheet": "<1.16",
"phpseclib/phpseclib": "<2.0.31|>=3,<3.0.7", "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.7",
@ -6880,6 +6881,7 @@
"thelia/thelia": ">=2.1-beta.1,<2.1.3", "thelia/thelia": ">=2.1-beta.1,<2.1.3",
"theonedemon/phpwhois": "<=4.2.5", "theonedemon/phpwhois": "<=4.2.5",
"thinkcmf/thinkcmf": "<=5.1.7", "thinkcmf/thinkcmf": "<=5.1.7",
"thorsten/phpmyfaq": "<=3.1.7",
"tinymce/tinymce": "<5.10", "tinymce/tinymce": "<5.10",
"titon/framework": ">=0,<9.9.99", "titon/framework": ">=0,<9.9.99",
"topthink/framework": "<=6.0.13", "topthink/framework": "<=6.0.13",
@ -6991,7 +6993,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-10-18T22:04:50+00:00" "time": "2022-10-19T23:05:04+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",

View File

@ -0,0 +1,84 @@
<?php
namespace App\Controller;
use App\Entity\Socket;
use App\Form\SocketType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/socket')]
class SocketController extends AbstractController
{
#[Route('/', name: 'socket_index', methods: ['GET'])]
public function index(EntityManagerInterface $entityManager): Response
{
$sockets = $entityManager
->getRepository(Socket::class)
->findAll();
return $this->render('socket/index.html.twig', [
'sockets' => $sockets,
]);
}
#[Route('/new', name: 'socket_new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $entityManager): Response
{
$socket = new Socket();
$form = $this->createForm(SocketType::class, $socket);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($socket);
$entityManager->flush();
return $this->redirectToRoute('socket_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('socket/new.html.twig', [
'socket' => $socket,
'form' => $form,
]);
}
#[Route('/{id}', name: 'socket_show', methods: ['GET'])]
public function show(Socket $socket): Response
{
return $this->render('socket/show.html.twig', [
'socket' => $socket,
]);
}
#[Route('/{id}/edit', name: 'socket_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Socket $socket, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(SocketType::class, $socket);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('socket_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('socket/edit.html.twig', [
'socket' => $socket,
'form' => $form,
]);
}
#[Route('/{id}', name: 'socket_delete', methods: ['POST'])]
public function delete(Request $request, Socket $socket, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$socket->getId(), $request->request->get('_token'))) {
$entityManager->remove($socket);
$entityManager->flush();
}
return $this->redirectToRoute('socket_index', [], Response::HTTP_SEE_OTHER);
}
}

View File

@ -2,13 +2,12 @@
namespace App\Entity; namespace App\Entity;
use App\Repository\BrandRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'brand', schema: 'collection')] #[ORM\Table(name: 'brand', schema: 'collection')]
#[ORM\Entity]//(repositoryClass: BrandRepository::class)] #[ORM\Entity]
#[ORM\UniqueConstraint(name: 'brand_unq', columns: ["name"])] #[ORM\UniqueConstraint(name: 'brand_unq', columns: ["name"])]
class Brand class Brand
{ {
@ -20,12 +19,15 @@ class Brand
#[ORM\SequenceGenerator(sequenceName: 'brand_id_seq', allocationSize: 1, initialValue: 1)] #[ORM\SequenceGenerator(sequenceName: 'brand_id_seq', allocationSize: 1, initialValue: 1)]
private int $id; private int $id;
/**
* @var Collection<int, BrandCategory>
*/
#[ORM\ManyToMany(targetEntity: BrandCategory::class)] #[ORM\ManyToMany(targetEntity: BrandCategory::class)]
#[ORM\JoinTable(name: 'collection.brand_category_link')] #[ORM\JoinTable(name: 'collection.brand_category_link')]
#[ORM\JoinColumn(name: 'brand_id', referencedColumnName: 'id')] #[ORM\JoinColumn(name: 'brand_id', referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name: 'brand_category', referencedColumnName: 'category_name')] #[ORM\InverseJoinColumn(name: 'brand_category', referencedColumnName: 'category_name')]
#[ORM\OrderBy(['name' => 'asc'])] #[ORM\OrderBy(['name' => 'asc'])]
private $categories; private Collection $categories;
#[ORM\Column(name: 'name', unique: TRUE, nullable: FALSE)] #[ORM\Column(name: 'name', unique: TRUE, nullable: FALSE)]
private string $name; private string $name;
@ -40,14 +42,6 @@ class Brand
return $this->name; return $this->name;
} }
/**
* @return Collection<int, BrandCategory>
*/
public function getCategories(): Collection
{
return $this->categories;
}
public function addCategory(BrandCategory $category): self public function addCategory(BrandCategory $category): self
{ {
if (!$this->categories->contains($category)) { if (!$this->categories->contains($category)) {

View File

@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use Doctrine\Common\Collections\{Collection, ArrayCollection};
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'cpu', schema: 'collection')] #[ORM\Table(name: 'cpu', schema: 'collection')]
@ -18,4 +19,36 @@ class Cpu {
#[ORM\OrderBy(['name' => 'asc'])] #[ORM\OrderBy(['name' => 'asc'])]
#[ORM\JoinColumn(name: 'brand_id', referencedColumnName: 'id', nullable: FALSE)] #[ORM\JoinColumn(name: 'brand_id', referencedColumnName: 'id', nullable: FALSE)]
private Brand $brand; private Brand $brand;
/**
* @var Collection<int, Socket>
*/
#[ORM\ManyToMany(targetEntity: Socket::class)]
#[ORM\JoinTable(name: 'collection.cpu_socket_link')]
#[ORM\JoinColumn(name: 'socket_id', referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name: 'cpu_id', referencedColumnName: 'id')]
#[ORM\OrderBy(['name' => 'asc'])]
private Collection $sockets;
public function __construct()
{
$this->sockets = new ArrayCollection();
}
public function addSocket(Socket $socket): self
{
if ( ! $this->sockets->contains($socket))
{
$this->sockets->add($socket);
}
return $this;
}
public function removeSocket(Socket $socket): self
{
$this->sockets->removeElement($socket);
return $this;
}
} }

43
src/Entity/Socket.php Normal file
View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
namespace App\Entity;
use App\Enum\SocketTypeEnum;
use Doctrine\ORM\Mapping as ORM;
/**
* @see https://en.wikipedia.org/wiki/CPU_socket
*/
#[ORM\Table(name: 'socket', schema: 'collection')]
#[ORM\Entity]
class Socket {
use GetSetTrait;
#[ORM\Column(name: 'id', type: 'integer', nullable: FALSE)]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private int $id;
#[ORM\Column(name: 'name', type: 'string', nullable: FALSE)]
private string $name;
#[ORM\Column(name: 'other_name', type: 'string', nullable: TRUE)]
private ?string $otherName = null;
#[ORM\Column(name: 'pin_count', type: 'integer', nullable: FALSE)]
private int $pinCount;
#[ORM\Column(
name: 'socket_type',
type: 'string',
enumType: SocketTypeEnum::class,
)]
private SocketTypeEnum $type = SocketTypeEnum::PIN_GRID_ARRAY;
public function __toString(): string
{
$name = ( ! empty($this->otherName)) ? "$this->name/$this->otherName" : $this->name;
return "$name ($this->type->value $this->pinCount)";
}
}

View File

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace App\Enum;
enum SocketTypeEnum: string {
case DUAL_INLINE_PACKAGE = 'DIP';
case LEAD_LESS_CHIP_CARRIER = 'LLCC';
case PLASTIC_LEADED_CHIP_CARRIER = 'PLCC';
case PIN_GRID_ARRAY = 'PGA';
case SLOT = 'Slot';
case LAND_GRID_ARRAY = 'LGA';
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace App\Form;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
trait BrandCategoryTrait {
public static function filterBrands(string $filter): callable
{
return static fn(EntityRepository $e) =>
$e->createQueryBuilder('b')
->join('b.categories', 'bc')
->where('bc.name=:name')
->orderBy('b.name', 'ASC')
->setParameter('name', $filter);
}
}

View File

@ -10,12 +10,14 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class GPUCoreType extends AbstractType class GPUCoreType extends AbstractType
{ {
use BrandCategoryTrait;
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder $builder
->add('brand', EntityType::class, [ ->add('brand', EntityType::class, [
'class' => Brand::class, 'class' => Brand::class,
'query_builder' => static fn (EntityRepository $e) => $e->createQueryBuilder('b')->orderBy('b.name', 'ASC'), 'query_builder' => self::filterBrands('gpu_core'),
]) ])
->add('name') ->add('name')
->add('variant') ->add('variant')
@ -33,3 +35,4 @@ class GPUCoreType extends AbstractType
]); ]);
} }
} }

View File

@ -12,14 +12,14 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use UnitEnum; use UnitEnum;
class GpuType extends AbstractType { class GpuType extends AbstractType {
use BrandCategoryTrait;
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$brandQueryBuilder = static fn(EntityRepository $e) => $e->createQueryBuilder('b')->orderBy('b.name', 'ASC');
$builder $builder
->add('gpuBrand', EntityType::class, [ ->add('gpuBrand', EntityType::class, [
'class' => Brand::class, 'class' => Brand::class,
'query_builder' => $brandQueryBuilder, 'query_builder' => self::filterBrands('gpu_core'),
]) ])
->add('modelName') ->add('modelName')
->add('gpuCore', EntityType::class, [ ->add('gpuCore', EntityType::class, [
@ -31,7 +31,7 @@ class GpuType extends AbstractType {
]) ])
->add('boardBrand', EntityType::class, [ ->add('boardBrand', EntityType::class, [
'class' => Brand::class, 'class' => Brand::class,
'query_builder' => $brandQueryBuilder, 'query_builder' => self::filterBrands('graphics_card'),
'empty_data' => NULL, 'empty_data' => NULL,
'placeholder' => 'Unknown', 'placeholder' => 'Unknown',
'required' => FALSE, 'required' => FALSE,

32
src/Form/SocketType.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Form;
use App\Entity\Socket;
use App\Enum\SocketTypeEnum;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EnumType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SocketType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
->add('otherName')
->add('pinCount')
->add('type', EnumType::class,[
'class' => SocketTypeEnum::class,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Socket::class,
]);
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20221019183925 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE collection.socket (id SERIAL NOT NULL, name VARCHAR(255) NOT NULL, other_name VARCHAR(255) DEFAULT NULL, pin_count INT NOT NULL, socket_type VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('DROP TABLE collection.socket');
}
}

View File

@ -1,21 +0,0 @@
<?php declare(strict_types=1);
namespace App\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
class BrandRepository extends EntityRepository {
public function filterByCategory(string $category): Query
{
$em = $this->getEntityManager();
$query = $em->createQuery("
SELECT b FROM App\Entity\Brand b
INNER JOIN b.categories c WHERE c.name = ?1
ORDER BY b.name ASC
");
$query->setParameter(1, $category);
return $query->execute();
}
}

View File

@ -9,7 +9,9 @@
<li class="{{ route starts with 'brand-category_' ? 'is-active' }}"> <li class="{{ route starts with 'brand-category_' ? 'is-active' }}">
<a href="{{ path('brand-category_index') }}">💃 Brand Categories</a> <a href="{{ path('brand-category_index') }}">💃 Brand Categories</a>
</li> </li>
<li class="{{ route starts with 'socket_' ? 'is-active' }}">
<a href="{{ path('socket_index') }}">📍Sockets</a>
</li>
<li class="not-implemented"> <li class="not-implemented">
<a href="#">🐏 Ram Types</a> <a href="#">🐏 Ram Types</a>
</li> </li>
@ -58,7 +60,13 @@
<a href="{{ path('gpu_index') }}">🎮 Graphics Cards</a> <a href="{{ path('gpu_index') }}">🎮 Graphics Cards</a>
</li> </li>
<li class="not-implemented"> <li class="not-implemented">
<a href="#">🧮 CPUs</a> <a href="#">🧠 CPUs</a>
</li>
<li class="not-implemented">
<a href="#">🧮 FPUs</a>
</li>
<li class="not-implemented">
<a href="#">🤰 Motherboards</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,27 @@
{% extends 'form.html.twig' %}
{% block title %}Edit Socket{% endblock %}
{% block form %}
<h1>Edit Socket</h1>
<div class="small callout">
<ul>
<li>
<a href="{{ path('socket_index') }}">Back to the list</a>
</li>
</ul>
</div>
<div class="large primary callout">
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="success button expanded">Update</button>
{{ form_end(form) }}
<form method="post" action="{{ path('socket_delete', {'id': socket.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ socket.id) }}">
<button type="submit" class="alert button expanded">Delete</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,54 @@
{% extends 'form.html.twig' %}
{% block title %}Socket{% endblock %}
{% block form %}
<h2>Sockets</h2>
<div class="small callout primary">
<ul>
<li>
<a href="{{ path('socket_new') }}">Add a Socket</a>
</li>
</ul>
</div>
<table class="hover scroll sortable stack">
<thead>
<tr>
<th>&nbsp;</th>
<th>Id</th>
<th>Name</th>
<th>OtherName</th>
<th>PinCount</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% for socket in sockets %}
<tr>
<td>
<ul>
<li>
<a href="{{ path('socket_show', {'id': socket.id}) }}">View 👁</a>
</li>
<li>
<a href="{{ path('socket_edit', {'id': socket.id}) }}">Edit <span class="edit-icon">&#9998;</span></a>
</li>
</ul>
</td>
<td>{{ socket.id }}</td>
<td>{{ socket.name }}</td>
<td>{{ socket.otherName }}</td>
<td>{{ socket.pinCount }}</td>
<td>{{ socket.type.value }}</td>
</tr>
{% else %}
<tr>
<td colspan="6">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends 'form.html.twig' %}
{% block title %}New Socket{% endblock %}
{% block form %}
<h2>Add a Socket</h2>
<div class="small callout">
<ul>
<li>
<a href="{{ path('socket_index') }}">Back to the list</a>
</li>
</ul>
</div>
<div class="large primary callout">
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="success button expanded">Add</button>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@ -0,0 +1,55 @@
{% extends 'form.html.twig' %}
{% block title %}Socket{% endblock %}
{% block form %}
<h2>Socket</h2>
<div class="callout">
<ul>
<li>
<a href="{{ path('socket_index') }}">Back to the list</a>
</li>
<li>
<a href="{{ path('socket_edit', { 'id': socket.id }) }}">Edit</a>
</li>
</ul>
<hr />
<form method="post" action="{{ path('socket_delete', {'id': socket.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ socket.id) }}">
<button type="submit" class="alert button expanded">Delete</button>
</form>
</div>
<div class="large primary callout">
<table class="table">
<tbody>
<tr>
<th>Id</th>
<td>{{ socket.id }}</td>
</tr>
<tr>
<th>Name</th>
<td>{{ socket.name }}</td>
</tr>
<tr>
<th>OtherName</th>
<td>{{ socket.otherName }}</td>
</tr>
<tr>
<th>PinCount</th>
<td>{{ socket.pinCount }}</td>
</tr>
<tr>
<th>Type</th>
<td>{{ socket.type.value }}</td>
</tr>
</tbody>
</table>
</div>
{% endblock %}